diff --git a/.server-changes/side-menu-project-and-org-menus.md b/.server-changes/side-menu-project-and-org-menus.md
new file mode 100644
index 0000000000..d64f0fd95e
--- /dev/null
+++ b/.server-changes/side-menu-project-and-org-menus.md
@@ -0,0 +1,20 @@
+---
+area: webapp
+type: improvement
+---
+
+Restructure the side menu's top-left and project/organization navigation:
+
+- Add a new "Project" section above the "Environment" section with a popover
+ that lists the org's projects (folder icon + checkmark for the selected one)
+ and a "New project" item at the bottom.
+- The top-left menu now shows the organization (avatar + org name, no
+ project/diagonal divider) and its popover is a clean list of org-level items
+ (Settings, Usage, Billing with plan badge, Billing alerts, Team, Private
+ connections, Roles, SSO, Vercel integration, Slack integration, Switch
+ organization, then Account and Logout) using the same icons and links as the
+ organization settings side menu.
+
+The org loader now exposes whether the RBAC and SSO plugins are installed so the
+side menu can gate the Roles and SSO items the same way the settings side menu
+does.
diff --git a/apps/webapp/app/components/navigation/EnvironmentSelector.tsx b/apps/webapp/app/components/navigation/EnvironmentSelector.tsx
index 40174acfe0..6701c07213 100644
--- a/apps/webapp/app/components/navigation/EnvironmentSelector.tsx
+++ b/apps/webapp/app/components/navigation/EnvironmentSelector.tsx
@@ -41,12 +41,16 @@ export function EnvironmentSelector({
environment,
className,
isCollapsed = false,
+ showConnector = false,
}: {
organization: MatchedOrganization;
project: SideMenuProject;
environment: SideMenuEnvironment;
className?: string;
isCollapsed?: boolean;
+ /** Show an end tree-connector to the left of the icon so the selector reads
+ * as connected to the Project menu above it. Only used in the side menu. */
+ showConnector?: boolean;
}) {
const { isManagedCloud } = useFeatures();
const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -71,6 +75,21 @@ export function EnvironmentSelector({
className
)}
>
+ {showConnector &&
+ !isCollapsed && (
+ // End tree-connector sized to the full button height (viewBox matches the
+ // 20x32 box) so the vertical line reaches the button's top edge and meets the
+ // Project button above, with the corner aligned to the environment icon's center.
+
+ )}
}
- content={environmentFullTitle(environment)}
+ content={`${environmentFullTitle(environment)} environment`}
side="right"
sideOffset={8}
- hidden={!isCollapsed}
+ delayDuration={isCollapsed ? 0 : 500}
buttonClassName="!h-8"
asChild
disableHoverableContent
@@ -111,6 +130,7 @@ export function EnvironmentSelector({
align="start"
style={{ maxHeight: `calc(var(--radix-popover-content-available-height) - 10vh)` }}
>
+
{project.environments
.filter((env) => env.parentEnvironmentId === null)
diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx
index 619d2477a4..e38fa86577 100644
--- a/apps/webapp/app/components/navigation/SideMenu.tsx
+++ b/apps/webapp/app/components/navigation/SideMenu.tsx
@@ -2,13 +2,12 @@ import {
ArrowTopRightOnSquareIcon,
ChevronRightIcon,
ExclamationTriangleIcon,
- PencilSquareIcon,
} from "@heroicons/react/24/outline";
-import { Link, useFetcher, useNavigation } from "@remix-run/react";
+import { LinkIcon } from "@heroicons/react/24/solid";
+import { useFetcher, useNavigation } from "@remix-run/react";
import { BugIcon } from "~/assets/icons/BugIcon";
import { LayoutGroup, motion } from "framer-motion";
import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
-import simplur from "simplur";
import { AIChatIcon } from "~/assets/icons/AIChatIcon";
import { AIPenIcon } from "~/assets/icons/AIPenIcon";
import { ArrowLeftRightIcon } from "~/assets/icons/ArrowLeftRightIcon";
@@ -41,6 +40,12 @@ import { TasksIcon } from "~/assets/icons/TasksIcon";
import { BellIcon } from "~/assets/icons/BellIcon";
import { UsageIcon } from "~/assets/icons/UsageIcon";
import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon";
+import { CreditCardIcon } from "~/assets/icons/CreditCardIcon";
+import { UserGroupIcon } from "~/assets/icons/UserGroupIcon";
+import { RolesIcon } from "~/assets/icons/RolesIcon";
+import { PadlockIcon } from "~/assets/icons/PadlockIcon";
+import { SlackIcon } from "~/assets/icons/SlackIcon";
+import { VercelLogo } from "~/components/integrations/VercelLogo";
import { Avatar } from "~/components/primitives/Avatar";
import { type MatchedEnvironment } from "~/hooks/useEnvironment";
import { useFeatureFlags } from "~/hooks/useFeatureFlags";
@@ -48,9 +53,14 @@ import { useFeatures } from "~/hooks/useFeatures";
import { type MatchedOrganization } from "~/hooks/useOrganizations";
import { type MatchedProject } from "~/hooks/useProject";
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
+import { useShowSelfServe } from "~/hooks/useShowSelfServe";
import { useHasAdminAccess } from "~/hooks/useUser";
import { type UserWithDashboardPreferences } from "~/models/user.server";
-import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";
+import {
+ useCurrentPlan,
+ useIsUsingRbacPlugin,
+ useIsUsingSsoPlugin,
+} from "~/routes/_app.orgs.$organizationSlug/route";
import { type FeedbackType } from "~/routes/resources.feedback";
import { IncidentStatusPanel, useIncidentStatus } from "~/routes/resources.incidents";
import { NotificationPanel } from "./NotificationPanel";
@@ -65,13 +75,19 @@ import {
newOrganizationPath,
newProjectPath,
organizationPath,
+ organizationRolesPath,
organizationSettingsPath,
+ organizationSlackIntegrationPath,
+ organizationSsoPath,
organizationTeamPath,
+ organizationVercelIntegrationPath,
queryPath,
regionsPath,
v3ApiKeysPath,
v3BatchesPath,
+ v3BillingLimitsPath,
v3BillingPath,
+ v3PrivateConnectionsPath,
v3DashboardsLandingPath,
v3BulkActionsPath,
v3DeploymentsPath,
@@ -99,9 +115,9 @@ import { ImpersonationBanner } from "../ImpersonationBanner";
import { Button, ButtonContent, LinkButton } from "../primitives/Buttons";
import { Dialog, DialogTrigger } from "../primitives/Dialog";
import { Paragraph } from "../primitives/Paragraph";
+import { Badge } from "../primitives/Badge";
import { Popover, PopoverContent, PopoverMenuItem, PopoverTrigger } from "../primitives/Popover";
import { ShortcutKey } from "../primitives/ShortcutKey";
-import { TextLink } from "../primitives/TextLink";
import {
SimpleTooltip,
Tooltip,
@@ -297,11 +313,9 @@ export function SideMenu({
)}
>
-
@@ -341,48 +355,53 @@ export function SideMenu({
>