feat(frontend): break out the sidebar into a reusable component + use it for admin page (#9618)

<!-- Clearly explain the need for these changes: -->
We need a sidebar for the admin page, might as well reuse the reusable
component to do so!

### Changes 🏗️
- Extracts the agptui sidebar to a more reusable component
- Update the usage of that sidebar in the settings page
- Use that same sidebar for the admin page

<!-- Concisely describe all of the changes made in this pull request:
-->

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [x] Test the old sidebar
  - [x] Test the new sidebar for admin
This commit is contained in:
Nicholas Tindle
2025-03-17 16:19:19 -05:00
committed by GitHub
parent 50eac43e1a
commit 841679216c
3 changed files with 103 additions and 216 deletions

View File

@@ -1,15 +1,29 @@
"use client";
import { ShoppingBag } from "lucide-react";
import { Sidebar } from "@/components/agptui/Sidebar";
import { Users, DollarSign, LogOut } from "lucide-react";
import { useState } from "react";
import Link from "next/link";
import { BinaryIcon, XIcon } from "lucide-react";
import { usePathname } from "next/navigation"; // Add this import
import { IconSliders } from "@/components/ui/icons";
const tabs = [
{ name: "Dashboard", href: "/admin/dashboard" },
{ name: "Marketplace", href: "/admin/marketplace" },
{ name: "Users", href: "/admin/users" },
{ name: "Settings", href: "/admin/settings" },
const sidebarLinkGroups = [
{
links: [
{
text: "Agent Management",
href: "/admin/agents",
icon: <Users className="h-6 w-6" />,
},
{
text: "User Spending",
href: "/admin/spending",
icon: <DollarSign className="h-6 w-6" />,
},
{
text: "Admin User Management",
href: "/admin/settings",
icon: <IconSliders className="h-6 w-6" />,
},
],
},
];
export default function AdminLayout({
@@ -17,84 +31,10 @@ export default function AdminLayout({
}: {
children: React.ReactNode;
}) {
const pathname = usePathname(); // Get the current pathname
const [activeTab, setActiveTab] = useState(() => {
// Set active tab based on the current route
return tabs.find((tab) => tab.href === pathname)?.name || tabs[0].name;
});
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<div className="min-h-screen bg-gray-100">
<nav className="bg-white shadow-sm">
<div className="max-w-10xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex h-16 items-center justify-between">
<div className="flex items-center">
<div className="flex-shrink-0">
<h1 className="text-xl font-bold">Admin Panel</h1>
</div>
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
{tabs.map((tab) => (
<Link
key={tab.name}
href={tab.href}
className={`${
activeTab === tab.name
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700"
} inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium`}
onClick={() => setActiveTab(tab.name)}
>
{tab.name}
</Link>
))}
</div>
</div>
<div className="sm:hidden">
<button
type="button"
className="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<span className="sr-only">Open main menu</span>
{mobileMenuOpen ? (
<XIcon className="block h-6 w-6" aria-hidden="true" />
) : (
<BinaryIcon className="block h-6 w-6" aria-hidden="true" />
)}
</button>
</div>
</div>
</div>
{mobileMenuOpen && (
<div className="sm:hidden">
<div className="space-y-1 pb-3 pt-2">
{tabs.map((tab) => (
<Link
key={tab.name}
href={tab.href}
className={`${
activeTab === tab.name
? "border-indigo-500 bg-indigo-50 text-indigo-700"
: "border-transparent text-gray-600 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-800"
} block border-l-4 py-2 pl-3 pr-4 text-base font-medium`}
onClick={() => {
setActiveTab(tab.name);
setMobileMenuOpen(false);
}}
>
{tab.name}
</Link>
))}
</div>
</div>
)}
</nav>
<main className="py-10">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">{children}</div>
</main>
<div className="flex min-h-screen w-screen max-w-[1360px] flex-col lg:flex-row">
<Sidebar linkGroups={sidebarLinkGroups} />
<div className="flex-1 pl-4">{children}</div>
</div>
);
}

View File

@@ -1,17 +1,48 @@
import * as React from "react";
import { Sidebar } from "@/components/agptui/Sidebar";
import {
IconDashboardLayout,
IconIntegrations,
IconProfile,
IconSliders,
IconCoin,
} from "@/components/ui/icons";
import { KeyIcon } from "lucide-react";
export default function Layout({ children }: { children: React.ReactNode }) {
const sidebarLinkGroups = [
{
links: [
{ text: "Creator Dashboard", href: "/profile/dashboard" },
{ text: "Agent dashboard", href: "/profile/agent-dashboard" },
{ text: "Billing", href: "/profile/credits" },
{ text: "Integrations", href: "/profile/integrations" },
{ text: "API Keys", href: "/profile/api_keys" },
{ text: "Profile", href: "/profile" },
{ text: "Settings", href: "/profile/settings" },
{
text: "Creator Dashboard",
href: "/profile/dashboard",
icon: <IconDashboardLayout className="h-6 w-6" />,
},
{
text: "Billing",
href: "/profile/credits",
icon: <IconCoin className="h-6 w-6" />,
},
{
text: "Integrations",
href: "/profile/integrations",
icon: <IconIntegrations className="h-6 w-6" />,
},
{
text: "API Keys",
href: "/profile/api_keys",
icon: <KeyIcon className="h-6 w-6" />,
},
{
text: "Profile",
href: "/profile",
icon: <IconProfile className="h-6 w-6" />,
},
{
text: "Settings",
href: "/profile/settings",
icon: <IconSliders className="h-6 w-6" />,
},
],
},
];

View File

@@ -2,27 +2,49 @@ import * as React from "react";
import Link from "next/link";
import { Button } from "./Button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { KeyIcon, Menu } from "lucide-react";
import {
IconDashboardLayout,
IconIntegrations,
IconProfile,
IconSliders,
IconCoin,
} from "../ui/icons";
import { Menu } from "lucide-react";
import { IconDashboardLayout } from "../ui/icons";
interface SidebarLinkGroup {
links: {
text: string;
href: string;
}[];
export interface SidebarLink {
text: string;
href: string;
icon?: React.ReactNode;
}
interface SidebarProps {
export interface SidebarLinkGroup {
links: SidebarLink[];
}
export interface SidebarProps {
linkGroups: SidebarLinkGroup[];
}
// Helper function to get the default icon component based on link text
const getDefaultIconForLink = () => {
// Default icon
return <IconDashboardLayout className="h-6 w-6" />;
};
export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
// Extract all links from linkGroups
const allLinks = linkGroups.flatMap((group) => group.links);
// Function to render link items
const renderLinks = () => {
return allLinks.map((link, index) => (
<Link
key={`${link.href}-${index}`}
href={link.href}
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
{link.icon || getDefaultIconForLink()}
<div className="p-ui-medium text-base font-medium leading-normal">
{link.text}
</div>
</Link>
));
};
return (
<>
<Sheet>
@@ -41,60 +63,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
>
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
<Link
href="/profile/dashboard"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<IconDashboardLayout className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
Creator dashboard
</div>
</Link>
<Link
href="/profile/credits"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<IconCoin className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
Billing
</div>
</Link>
<Link
href="/profile/integrations"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<IconIntegrations className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
Integrations
</div>
</Link>
<Link
href="/profile/api_keys"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<KeyIcon className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
API Keys
</div>
</Link>
<Link
href="/profile"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<IconProfile className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
Profile
</div>
</Link>
<Link
href="/profile/settings"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<IconSliders className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
Settings
</div>
</Link>
{renderLinks()}
</div>
</div>
</SheetContent>
@@ -103,60 +72,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
<div className="relative hidden h-[912px] w-[234px] border-none lg:block">
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
<Link
href="/profile/dashboard"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<IconDashboardLayout className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
Agent dashboard
</div>
</Link>
<Link
href="/profile/credits"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<IconCoin className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
Billing
</div>
</Link>
<Link
href="/profile/integrations"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<IconIntegrations className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
Integrations
</div>
</Link>
<Link
href="/profile/api_keys"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<KeyIcon className="h-6 w-6" strokeWidth={1} />
<div className="p-ui-medium text-base font-medium leading-normal">
API Keys
</div>
</Link>
<Link
href="/profile"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<IconProfile className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
Profile
</div>
</Link>
<Link
href="/profile/settings"
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
<IconSliders className="h-6 w-6" />
<div className="p-ui-medium text-base font-medium leading-normal">
Settings
</div>
</Link>
{renderLinks()}
</div>
</div>
</div>