diff --git a/apps/console/src/App.tsx b/apps/console/src/App.tsx index 04ff2d8db..f3847d862 100644 --- a/apps/console/src/App.tsx +++ b/apps/console/src/App.tsx @@ -24,7 +24,7 @@ posthog.init(process.env.POSTHOG_KEY!, { }); const OrganizationSelectionPage = lazy( - () => import("./pages/OrganizationSelectionPage") + () => import("./pages/OrganizationSelectionPage"), ); const HomePage = lazy(() => import("./pages/HomePage")); const NotFoundPage = lazy(() => import("./pages/NotFoundPage")); @@ -35,14 +35,14 @@ const VendorOverviewPage = lazy(() => import("./pages/VendorOverviewPage")); const SettingsPage = lazy(() => import("./pages/SettingsPage")); const CreatePeoplePage = lazy(() => import("./pages/CreatePeoplePage")); const FrameworkOverviewPage = lazy( - () => import("./pages/FrameworkOverviewPage") + () => import("./pages/FrameworkOverviewPage"), ); const ControlOverviewPage = lazy(() => import("./pages/ControlOverviewPage")); const PeopleOverviewPage = lazy(() => import("./pages/PeopleOverviewPage")); const LoginPage = lazy(() => import("./pages/LoginPage")); const RegisterPage = lazy(() => import("./pages/RegisterPage")); const CreateOrganizationPage = lazy( - () => import("./pages/CreateOrganizationPage") + () => import("./pages/CreateOrganizationPage"), ); function App() { diff --git a/apps/console/src/components/AppSidebar.tsx b/apps/console/src/components/AppSidebar.tsx index 3ea4852ef..7d766af09 100644 --- a/apps/console/src/components/AppSidebar.tsx +++ b/apps/console/src/components/AppSidebar.tsx @@ -32,34 +32,43 @@ import { import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/AppSidebarQuery.graphql"; import { TeamSwitcher } from "@/components/TeamSwitcher"; -function getNavItems(organizationId: string) { +function getNavItems(organizationId?: string) { + // Always return the same structure, but with or without URLs depending on whether an organization is selected return { navMain: [ { title: "Frameworks", - url: `/organizations/${organizationId}/frameworks`, + url: organizationId + ? `/organizations/${organizationId}/frameworks` + : undefined, icon: BookOpen, }, { title: "Organizations", icon: Building, - url: `/organizations/${organizationId}/peoples`, - items: [ - { - title: "Peoples", - url: `/organizations/${organizationId}/peoples`, - icon: Users, - }, - { - title: "Vendors", - url: `/organizations/${organizationId}/vendors`, - icon: ToyBrick, - }, - ], + url: organizationId + ? `/organizations/${organizationId}/peoples` + : undefined, + items: organizationId + ? [ + { + title: "Peoples", + url: `/organizations/${organizationId}/peoples`, + icon: Users, + }, + { + title: "Vendors", + url: `/organizations/${organizationId}/vendors`, + icon: ToyBrick, + }, + ] + : [], }, { title: "Settings", - url: `/organizations/${organizationId}/settings`, + url: organizationId + ? `/organizations/${organizationId}/settings` + : undefined, icon: Settings, }, ], @@ -96,7 +105,7 @@ function AppSidebarContent({ }) { const { organizationId } = useParams(); const data = usePreloadedQuery(AppSidebarQuery, queryRef); - const navItems = getNavItems(organizationId!); + const navItems = getNavItems(organizationId); return ( diff --git a/apps/console/src/components/NavMain.tsx b/apps/console/src/components/NavMain.tsx index 4a1af936e..78e9dff4c 100644 --- a/apps/console/src/components/NavMain.tsx +++ b/apps/console/src/components/NavMain.tsx @@ -1,7 +1,7 @@ "use client"; import { ChevronRight, type LucideIcon } from "lucide-react"; -import { Link, useLocation } from "react-router"; +import { Link, useLocation, useParams } from "react-router"; import { Collapsible, @@ -35,15 +35,62 @@ export function NavMain({ }[]; }) { const location = useLocation(); + const { organizationId } = useParams(); + + const noOrganizationSelected = organizationId === undefined; + + if (noOrganizationSelected) { + return ( + + +
+ + + {/* First item - simple item */} + + +
+
+ + + + {/* Second item - with dropdown */} + + +
+
+
+
+
+ + + {[1, 2].map((i) => ( + + +
+ + + ))} + + + + {/* Third item - simple item */} + + +
+
+ + + + + ); + } - // Determine if an item is active based on the current route const isItemActive = (item: { url?: string; items?: { url: string }[] }) => { - // If the item has a URL and it matches the current path if (item.url && location.pathname.startsWith(item.url)) { return true; } - // If the item has sub-items, check if any of them match the current path if (item.items?.length) { return item.items.some((subItem) => location.pathname.startsWith(subItem.url), diff --git a/apps/console/src/components/NavSecondary.tsx b/apps/console/src/components/NavSecondary.tsx index aee870402..cbd09cf2d 100644 --- a/apps/console/src/components/NavSecondary.tsx +++ b/apps/console/src/components/NavSecondary.tsx @@ -1,5 +1,6 @@ import * as React from "react"; import { type LucideIcon } from "lucide-react"; +import { useLocation } from "react-router"; import { SidebarGroup, @@ -19,17 +20,53 @@ export function NavSecondary({ icon: LucideIcon; }[]; } & React.ComponentPropsWithoutRef) { + const location = useLocation(); + const noOrganizationSelected = location.pathname === "/"; + + if (noOrganizationSelected) { + return ( + + + + {[1, 2].map((i) => ( + + +
+
+ + + ))} + + + + ); + } + return ( {items.map((item) => ( - - - - {item.title} - + + {item.url !== "#" ? ( + + + {item.title} + + ) : ( + <> + + {item.title} + + )} ))} diff --git a/apps/console/src/components/NavUser.tsx b/apps/console/src/components/NavUser.tsx index 97620449e..65acf9584 100644 --- a/apps/console/src/components/NavUser.tsx +++ b/apps/console/src/components/NavUser.tsx @@ -9,7 +9,7 @@ import { Sparkles, } from "lucide-react"; import { graphql, useFragment } from "react-relay"; -import { useNavigate } from "react-router"; +import { useNavigate, useParams } from "react-router"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { @@ -43,12 +43,31 @@ export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) { const currentUser = useFragment(navUserFragment, viewer); const { logout } = useAuth(); const navigate = useNavigate(); + const { organizationId } = useParams(); + const noOrganizationSelected = !organizationId; const handleLogout = async () => { await logout(); navigate("/login"); }; + if (noOrganizationSelected) { + return ( + + + +
+
+
+
+
+
+ + + + ); + } + return ( diff --git a/apps/console/src/components/TeamSwitcher.tsx b/apps/console/src/components/TeamSwitcher.tsx index 3eac4bbdb..34cca4a43 100644 --- a/apps/console/src/components/TeamSwitcher.tsx +++ b/apps/console/src/components/TeamSwitcher.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect } from "react"; -import { ChevronsUpDown, Plus } from "lucide-react"; +import { ChevronsUpDown, Plus, Building } from "lucide-react"; import { graphql, useFragment } from "react-relay"; import { Link, useNavigate, useParams } from "react-router"; @@ -67,26 +67,41 @@ export function TeamSwitcher({ const { organizationId } = useParams(); const [currentOrganization, setCurrentOrganization] = useState(null); + const hasOrganizations = + data.organizations && data.organizations.edges.length > 0; useEffect(() => { - if (data.organizations && data.organizations.edges.length > 0) { + if (hasOrganizations) { const org = data.organizations.edges.find( - (edge) => edge.node.id === organizationId + (edge) => edge.node.id === organizationId, ); if (org) { setCurrentOrganization(org.node); } } - }, [data.organizations, organizationId]); - - if (!currentOrganization) { - return null; - } + }, [data.organizations, organizationId, hasOrganizations]); const handleOrganizationSwitch = (org: Organization) => { navigate(`/organizations/${org.id}`); }; + if (!hasOrganizations) { + return ( + + + +
+
+
+
+
+
+ + + + ); + } + return ( @@ -94,18 +109,42 @@ export function TeamSwitcher({
- + {currentOrganization ? ( + + ) : ( + + )}
- - {currentOrganization.name} + + {currentOrganization + ? currentOrganization.name + : "Select Organization"} + + + {currentOrganization ? "Free" : "No organization selected"} - Free
- +
Organizations - {data.organizations.edges.map((edge, index) => ( - handleOrganizationSwitch(edge.node)} - className="gap-2 p-2" - > -
- -
- {edge.node.name} - ⌘{index + 1} -
- ))} + {hasOrganizations && + data.organizations.edges.map((edge, index) => ( + handleOrganizationSwitch(edge.node)} + className={`gap-2 p-2 ${ + edge.node.id === organizationId ? "bg-muted" : "" + }`} + > +
+ +
+ {edge.node.name} + ⌘{index + 1} +
+ ))}