From 1d44507553fe97d8a50ebdc4ce5cd3da3e750b97 Mon Sep 17 00:00:00 2001 From: tylerslaton Date: Tue, 22 Oct 2024 13:44:02 -0400 Subject: [PATCH] feat: overhaul sidebar and header The sidebar now uses the ShadCN component. This reduces code complexity by a lot. Throughout the applications the various components for this are used and overall styling has changed quite a bit. As such, the old sidebar components are no longer needed and are deleted. In addition, the header now has breadcrumbs which are used to display the current page in a tree format replacing the previous method of just displaying a heading. Finally, the sidebar has been updated to have a settings menu which contains a link to the OAuth applications page. Signed-off-by: tylerslaton --- ui/admin/app/components/header/HeaderNav.tsx | 229 ++--- ui/admin/app/components/sidebar/Sidebar.tsx | 163 +++- .../components/sidebar/SidebarCollapsed.tsx | 35 - .../app/components/sidebar/SidebarFull.tsx | 40 - .../app/components/sidebar/SidebarSection.tsx | 35 - ui/admin/app/components/sidebar/index.tsx | 2 +- ui/admin/app/components/ui/breadcrumb.tsx | 115 +++ ui/admin/app/components/ui/sheet.tsx | 17 +- ui/admin/app/components/ui/sidebar.tsx | 787 ++++++++++++++++++ ui/admin/app/components/ui/skeleton.tsx | 15 + ui/admin/app/components/ui/tooltip.tsx | 28 +- ui/admin/app/components/user/UserMenu.tsx | 28 +- ui/admin/app/hooks/use-mobile.tsx | 23 + ui/admin/app/root.tsx | 9 +- ui/admin/app/routes/_auth.agents.$agent.tsx | 2 +- ui/admin/app/routes/_auth.agents._index.tsx | 5 +- ui/admin/app/routes/_auth.threads.tsx | 3 +- ui/admin/app/routes/_auth.tsx | 14 +- ui/admin/app/routes/_auth.users.tsx | 3 +- ui/admin/app/tailwind.css | 16 + ui/admin/package-lock.json | 2 +- ui/admin/package.json | 2 +- ui/admin/tailwind.config.ts | 12 + 23 files changed, 1239 insertions(+), 346 deletions(-) delete mode 100644 ui/admin/app/components/sidebar/SidebarCollapsed.tsx delete mode 100644 ui/admin/app/components/sidebar/SidebarFull.tsx delete mode 100644 ui/admin/app/components/sidebar/SidebarSection.tsx create mode 100644 ui/admin/app/components/ui/breadcrumb.tsx create mode 100644 ui/admin/app/components/ui/sidebar.tsx create mode 100644 ui/admin/app/components/ui/skeleton.tsx create mode 100644 ui/admin/app/hooks/use-mobile.tsx diff --git a/ui/admin/app/components/header/HeaderNav.tsx b/ui/admin/app/components/header/HeaderNav.tsx index a46c6099..ca9254bc 100644 --- a/ui/admin/app/components/header/HeaderNav.tsx +++ b/ui/admin/app/components/header/HeaderNav.tsx @@ -1,29 +1,27 @@ -import { Link, useLocation, useParams } from "@remix-run/react"; -import { ArrowLeftIcon, MenuIcon } from "lucide-react"; -import { $params, $path } from "remix-routes"; +import { Link, Params, useLocation, useParams } from "@remix-run/react"; +import { $path } from "remix-routes"; import useSWR from "swr"; import { AgentService } from "~/lib/service/api/agentService"; import { ThreadsService } from "~/lib/service/api/threadsService"; -import { QueryParamSchemas } from "~/lib/service/routeQueryParams"; -import { cn, parseQueryParams } from "~/lib/utils"; +import { cn } from "~/lib/utils"; import { DarkModeToggle } from "~/components/DarkModeToggle"; -import { TypographyH4, TypographySmall } from "~/components/Typography"; -import { OttoLogo } from "~/components/branding/OttoLogo"; -import { useLayout } from "~/components/layout/LayoutProvider"; -import { Button } from "~/components/ui/button"; -import { UserMenu } from "~/components/user/UserMenu"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "~/components/ui/breadcrumb"; + +import { SidebarTrigger } from "../ui/sidebar"; +import { UserMenu } from "../user/UserMenu"; export function HeaderNav() { - const { - isExpanded, - onExpandedChange, - smallSidebarWidth, - fullSidebarWidth, - } = useLayout(); - const { pathname } = useLocation(); + const params = useParams(); const headerHeight = "h-[60px]"; return ( @@ -34,35 +32,11 @@ export function HeaderNav() { )} >
-
- - - -
-
- - {getHeaderContent(pathname)} - + +
+ {getBreadcrumbs(pathname, params)}
@@ -75,115 +49,90 @@ export function HeaderNav() { ); } -function getHeaderContent(route: string) { - if (new RegExp($path("/agents/:agent", { agent: "(.*)" })).test(route)) { - return ; - } - - if (new RegExp($path("/agents")).test(route)) { - return <>Agents; - } - - if (new RegExp($path("/threads")).test(route)) { - return ; - } - - if (new RegExp($path("/thread/:id", { id: "(.*)" })).test(route)) { - return ; - } - - if (new RegExp($path("/users")).test(route)) { - return <>Users; - } -} - -const AgentEditContent = () => { - const { from } = - parseQueryParams(window.location.href, QueryParamSchemas.Agents).data || - {}; - - const params = useParams(); - const { agent: agentId } = $params("/agents/:agent", params); - - const { data: agent } = useSWR( - AgentService.getAgentById.key(agentId), - ({ agentId }) => AgentService.getAgentById(agentId) - ); - +function getBreadcrumbs(route: string, params: Readonly>) { return ( -
- {from && ( - - )} - {agent?.name || "New Agent"} -
- ); -}; - -const ThreadsContent = () => { - const { data: { agentId = null } = {}, success } = parseQueryParams( - window.location.href, - QueryParamSchemas.Threads - ); - - const { data: threads } = useSWR( - ThreadsService.getThreadsByAgent.key(agentId), - ({ agentId }) => ThreadsService.getThreadsByAgent(agentId) + + + + + Home + + + + {new RegExp($path("/agents/:agent", { agent: "(.*)" })).test( + route + ) ? ( + <> + + + Agents + + + + + + + + + + ) : ( + new RegExp($path("/agents")).test(route) && ( + + Agents + + ) + )} + {new RegExp($path("/threads")).test(route) && ( + + Threads + + )} + {new RegExp($path("/thread/:id", { id: "(.*)" })).test( + route + ) && ( + <> + + + Threads + + + + + + + + + + )} + {new RegExp($path("/users")).test(route) && ( + + Users + + )} + {new RegExp($path("/oauth-apps")).test(route) && ( + + OAuth Apps + + )} + + ); +} +const AgentName = ({ agentId }: { agentId: string }) => { const { data: agent } = useSWR( AgentService.getAgentById.key(agentId), ({ agentId }) => AgentService.getAgentById(agentId) ); - if (!success) return <>Threads; - - return ( -
- Threads - - {agentId && ( - - - Showing {threads?.length} threads - - | - - Agent: {agent?.name ?? agentId} - - - )} -
- ); + return <>{agent?.name || "New Agent"}; }; -const ThreadContent = () => { - const params = useParams(); - const { id: threadId } = $params("/thread/:id", params); - +const ThreadName = ({ threadId }: { threadId: string }) => { const { data: thread } = useSWR( ThreadsService.getThreadById.key(threadId), ({ threadId }) => ThreadsService.getThreadById(threadId) ); - const { data: agent } = useSWR( - AgentService.getAgentById.key(thread?.agentID), - ({ agentId }) => AgentService.getAgentById(agentId) - ); - - return ( -
- {agent?.name && ( - <> - {agent?.name} - - - - )} - {thread?.description && {thread?.description}} -
- ); + return <>{thread?.description || threadId}; }; diff --git a/ui/admin/app/components/sidebar/Sidebar.tsx b/ui/admin/app/components/sidebar/Sidebar.tsx index a6a773d9..cb666a82 100644 --- a/ui/admin/app/components/sidebar/Sidebar.tsx +++ b/ui/admin/app/components/sidebar/Sidebar.tsx @@ -1,49 +1,134 @@ -import React from "react"; +import { Link } from "@remix-run/react"; +import { + BotIcon, + KeyIcon, + MessageSquare, + SettingsIcon, + User, +} from "lucide-react"; +import { $path } from "remix-routes"; import { cn } from "~/lib/utils"; -import { SidebarFull } from "~/components/sidebar/SidebarFull"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "~/components/ui/popover"; +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupContent, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarRail, + useSidebar, +} from "~/components/ui/sidebar"; -import { useLayout } from "../layout/LayoutProvider"; -import { SidebarCollapsed } from "./SidebarCollapsed"; +import { OttoLogo } from "../branding/OttoLogo"; +import { Button } from "../ui/button"; -type SidebarProps = React.HTMLAttributes; - -export function Sidebar({ className, ...props }: SidebarProps) { - const { isExpanded, sidebarWidth } = useLayout(); +// Menu items. +const items = [ + { + title: "Agents", + url: $path("/agents"), + icon: BotIcon, + }, + { + title: "Threads", + url: $path("/threads"), + icon: MessageSquare, + }, + { + title: "Users", + url: $path("/users"), + icon: User, + }, +]; +export function AppSidebar() { + const { state } = useSidebar(); return ( -
-
-
-
- -
-
- -
-
- -
+ + + +
+
-
-
+ + + + + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + + + + + + + + + Settings + + + + + + + + ); } diff --git a/ui/admin/app/components/sidebar/SidebarCollapsed.tsx b/ui/admin/app/components/sidebar/SidebarCollapsed.tsx deleted file mode 100644 index dd26fe2c..00000000 --- a/ui/admin/app/components/sidebar/SidebarCollapsed.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { BotIcon, Key, MessageSquare, User } from "lucide-react"; -import { Link } from "react-router-dom"; -import { $path } from "remix-routes"; - -import { Button } from "~/components/ui/button"; - -export function SidebarCollapsed() { - return ( -
- - - - - - - -
- ); -} diff --git a/ui/admin/app/components/sidebar/SidebarFull.tsx b/ui/admin/app/components/sidebar/SidebarFull.tsx deleted file mode 100644 index 00d26a59..00000000 --- a/ui/admin/app/components/sidebar/SidebarFull.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { BotIcon, Key, MessageSquare, User } from "lucide-react"; -import { $path } from "remix-routes"; - -import { cn } from "~/lib/utils"; - -import { SidebarSection } from "~/components/sidebar/SidebarSection"; -import { ScrollArea } from "~/components/ui/scroll-area"; - -type SidebarFullProps = React.HTMLAttributes; - -export function SidebarFull({ className }: SidebarFullProps) { - return ( -
- -
- } - /> - } - /> - } - /> - } - /> -
-
-
- ); -} diff --git a/ui/admin/app/components/sidebar/SidebarSection.tsx b/ui/admin/app/components/sidebar/SidebarSection.tsx deleted file mode 100644 index bb993139..00000000 --- a/ui/admin/app/components/sidebar/SidebarSection.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Link } from "react-router-dom"; - -import { Button } from "~/components/ui/button"; - -type SidebarSectionProps = { - title: string; - linkTo: string; - children?: React.ReactNode; - icon?: React.ReactNode; -}; - -export function SidebarSection({ - title, - linkTo, - children, - icon, -}: SidebarSectionProps) { - return ( -
- -
{children}
-
- ); -} diff --git a/ui/admin/app/components/sidebar/index.tsx b/ui/admin/app/components/sidebar/index.tsx index df73ac99..f0ca56e5 100644 --- a/ui/admin/app/components/sidebar/index.tsx +++ b/ui/admin/app/components/sidebar/index.tsx @@ -1 +1 @@ -export { Sidebar } from "~/components/sidebar/Sidebar"; +export { AppSidebar as Sidebar } from "~/components/sidebar/Sidebar"; diff --git a/ui/admin/app/components/ui/breadcrumb.tsx b/ui/admin/app/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..7569b231 --- /dev/null +++ b/ui/admin/app/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons"; +import { Slot } from "@radix-ui/react-slot"; +import * as React from "react"; + +import { cn } from "~/lib/utils"; + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode; + } +>(({ ...props }, ref) =>