Launched best Saas Marketing template at cheap — Check out

Admin Dashboard

A comprehensive admin dashboard with real-time analytics, user management, and system monitoring. Features custom components with minimal shadcn usage.

Folder structure

admin-sidebar.tsx
dashboard-card.tsx
dashboard-header.tsx
quick-actions.tsx
recent-activity.tsx
revenue-chart.tsx
system-status.tsx
users-table.tsx
index.tsx

Installation and setup

To set up the Admin Dashboard, follow these steps:

Add admin-sidebar, dashboard-card, dashboard-header, quick-actions, recent-activity, revenue-chart, system-status, and user-table components to your project.

Run the following command to install the necessary components:

npx mvpblocks add admin-sidebar dashboard-card dashboard-header quick-actions recent-activity revenue-chart system-status users-table

Copy the code from the following files and paste it into your project:

Add admin-sidebar.tsx:

'use client';import { memo } from 'react';import { useTheme } from 'next-themes';import Link from 'next/link';import {  Sidebar,  SidebarContent,  SidebarFooter,  SidebarGroup,  SidebarGroupContent,  SidebarGroupLabel,  SidebarHeader,  SidebarMenu,  SidebarMenuButton,  SidebarMenuItem,  SidebarRail,} from '@/components/ui/sidebar';import {  LayoutDashboard,  Users,  BarChart3,  FileText,  Activity,  Database,  Shield,  Zap,  Bell,  Settings,  Moon,  Sun,  User,} from 'lucide-react';const menuItems = [  { title: 'Dashboard', icon: LayoutDashboard, href: '#dashboard' },  { title: 'Analytics', icon: BarChart3, href: '#analytics' },  { title: 'Users', icon: Users, href: '#users' },  { title: 'Content', icon: FileText, href: '#content' },  { title: 'Activity', icon: Activity, href: '#activity' },  { title: 'Database', icon: Database, href: '#database' },  { title: 'Security', icon: Shield, href: '#security' },  { title: 'Performance', icon: Zap, href: '#performance' },  { title: 'Notifications', icon: Bell, href: '#notifications' },  { title: 'Settings', icon: Settings, href: '#settings' },];export const AdminSidebar = memo(() => {  const { theme, setTheme } = useTheme();  return (    <Sidebar collapsible="icon">      <SidebarHeader>        <SidebarMenu>          <SidebarMenuItem>            <SidebarMenuButton size="lg" asChild>              <Link prefetch={false} href="#dashboard">                <div className="bg-primary text-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">                  <LayoutDashboard className="h-5 w-5" />                </div>                <div className="grid flex-1 text-left text-sm leading-tight">                  <span className="truncate font-semibold">TechCorp</span>                  <span className="truncate text-xs">Admin Panel</span>                </div>              </Link>            </SidebarMenuButton>          </SidebarMenuItem>        </SidebarMenu>      </SidebarHeader>      <SidebarContent>        <SidebarGroup>          <SidebarGroupLabel>Navigation</SidebarGroupLabel>          <SidebarGroupContent>            <SidebarMenu>              {menuItems.map((item) => {                const Icon = item.icon;                return (                  <SidebarMenuItem key={item.href}>                    <SidebarMenuButton asChild>                      <Link prefetch={false} href={item.href}>                        <Icon />                        <span>{item.title}</span>                      </Link>                    </SidebarMenuButton>                  </SidebarMenuItem>                );              })}            </SidebarMenu>          </SidebarGroupContent>        </SidebarGroup>      </SidebarContent>      <SidebarFooter>        <SidebarMenu>          <SidebarMenuItem>            <SidebarMenuButton              onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}            >              {theme === 'dark' ? <Sun /> : <Moon />}              <span>{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}</span>            </SidebarMenuButton>          </SidebarMenuItem>          <SidebarMenuItem>            <SidebarMenuButton asChild>              <Link prefetch={false} href="#profile">                <User />                <span>Admin Profile</span>              </Link>            </SidebarMenuButton>          </SidebarMenuItem>        </SidebarMenu>      </SidebarFooter>      <SidebarRail />    </Sidebar>  );});AdminSidebar.displayName = 'AdminSidebar';

Add dashboard-card.tsx:

'use client';import { memo } from 'react';import { motion } from 'framer-motion';import { TrendingUp } from 'lucide-react';interface DashboardCardProps {  stat: {    title: string;    value: string;    change: string;    changeType: 'positive' | 'negative';    icon: any;    color: string;    bgColor: string;  };  index: number;}export const DashboardCard = memo(({ stat, index }: DashboardCardProps) => {  const Icon = stat.icon;  return (    <motion.div      initial={{ opacity: 0, y: 20 }}      animate={{ opacity: 1, y: 0 }}      transition={{ delay: index * 0.1 }}      whileHover={{ scale: 1.02, transition: { duration: 0.2 } }}      className="group relative cursor-pointer"    >      <div className="border-border bg-card/40 rounded-xl border p-6 transition-all duration-300 hover:shadow-lg">        <div className="to-primary/5 absolute inset-0 rounded-xl bg-gradient-to-br from-transparent via-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />        <div className="relative">          <div className="mb-4 flex items-center justify-between">            <div className={`rounded-lg p-3 ${stat.bgColor}`}>              <Icon className={`h-6 w-6 ${stat.color}`} />            </div>            <div              className={`flex items-center gap-1 text-sm font-medium ${                stat.changeType === 'positive'                  ? 'text-green-500'                  : 'text-red-500'              }`}            >              <TrendingUp                className={`h-4 w-4 ${                  stat.changeType === 'negative' ? 'rotate-180' : ''                }`}              />              <span>{stat.change}</span>            </div>          </div>          <div className="mb-3">            <h3 className="text-foreground mb-1 text-3xl font-bold">              {stat.value}            </h3>            <p className="text-muted-foreground text-sm font-medium">              {stat.title}            </p>          </div>          <div className="bg-muted h-2 overflow-hidden rounded-full">            <motion.div              initial={{ width: 0 }}              animate={{ width: `${65 + index * 8}%` }}              transition={{ duration: 1, delay: index * 0.1 }}              className={`h-full rounded-full ${stat.color.replace('text-', 'bg-')}`}            />          </div>        </div>      </div>    </motion.div>  );});DashboardCard.displayName = 'DashboardCard';

Add dashboard-header.tsx:

'use client';import { memo } from 'react';import { motion } from 'framer-motion';import { Button } from '@/components/ui/button';import { Input } from '@/components/ui/input';import { SidebarTrigger } from '@/components/ui/sidebar';import { Separator } from '@/components/ui/separator';import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuItem,  DropdownMenuTrigger,} from '@/components/ui/dropdown-menu';import {  Breadcrumb,  BreadcrumbItem,  BreadcrumbLink,  BreadcrumbList,  BreadcrumbPage,  BreadcrumbSeparator,} from '@/components/ui/breadcrumb';import {  Bell,  Search,  Filter,  Download,  RefreshCw,  MoreHorizontal,} from 'lucide-react';interface DashboardHeaderProps {  searchQuery: string;  onSearchChange: (value: string) => void;  onRefresh: () => void;  onExport: () => void;  isRefreshing: boolean;}export const DashboardHeader = memo(  ({    searchQuery,    onSearchChange,    onRefresh,    onExport,    isRefreshing,  }: DashboardHeaderProps) => {    return (      <header className="bg-background/95 sticky top-0 z-50 flex h-16 w-full shrink-0 items-center gap-2 border-b backdrop-blur transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">        <div className="flex items-center gap-2 px-4">          <SidebarTrigger className="-ml-1" />          <Separator orientation="vertical" className="mr-2 h-4" />          <Breadcrumb>            <BreadcrumbList>              <BreadcrumbItem className="hidden md:block">                <BreadcrumbLink href="#">Home</BreadcrumbLink>              </BreadcrumbItem>            </BreadcrumbList>          </Breadcrumb>        </div>        <div className="ml-auto flex items-center gap-2 px-4">          <motion.div            initial={{ opacity: 0, x: 20 }}            animate={{ opacity: 1, x: 0 }}            className="flex items-center gap-2"          >            {/* Search Input - Hide on Mobile */}            <div className="relative hidden md:block">              <Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform" />              <Input                placeholder="Search..."                value={searchQuery}                onChange={(e) => onSearchChange(e.target.value)}                className="w-64 pl-10"              />            </div>            {/* Desktop Actions */}            <div className="hidden items-center gap-2 md:flex">              <Button variant="outline" size="sm">                <Filter className="mr-2 h-4 w-4" />                Filter              </Button>              <Button variant="outline" size="sm" onClick={onExport}>                <Download className="mr-2 h-4 w-4" />                Export              </Button>              <Button                variant="outline"                size="sm"                onClick={onRefresh}                disabled={isRefreshing}              >                <RefreshCw                  className={`mr-2 h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`}                />                Refresh              </Button>            </div>            {/* Mobile Menu */}            <DropdownMenu>              <DropdownMenuTrigger asChild className="md:hidden">                <Button variant="outline" size="icon">                  <MoreHorizontal className="h-4 w-4" />                </Button>              </DropdownMenuTrigger>              <DropdownMenuContent align="end" className="w-48">                <DropdownMenuItem onClick={() => onSearchChange('')}>                  <Search className="mr-2 h-4 w-4" />                  Search                </DropdownMenuItem>                <DropdownMenuItem>                  <Filter className="mr-2 h-4 w-4" />                  Filter                </DropdownMenuItem>                <DropdownMenuItem onClick={onExport}>                  <Download className="mr-2 h-4 w-4" />                  Export                </DropdownMenuItem>                <DropdownMenuItem onClick={onRefresh}>                  <RefreshCw className="mr-2 h-4 w-4" />                  Refresh                </DropdownMenuItem>              </DropdownMenuContent>            </DropdownMenu>            <Button variant="outline" size="sm">              <Bell className="h-4 w-4" />            </Button>          </motion.div>        </div>      </header>    );  },);DashboardHeader.displayName = 'DashboardHeader';

Add quick-actions.tsx:

'use client';import { memo } from 'react';import { motion } from 'framer-motion';import { Button } from '@/components/ui/button';import { Users, BarChart3, Download, Settings } from 'lucide-react';interface QuickActionsProps {  onAddUser: () => void;  onExport: () => void;}const actions = [  {    icon: Users,    label: 'Add New User',    color: 'blue',    shortcut: 'Ctrl+N',    action: 'addUser',  },  {    icon: BarChart3,    label: 'View Analytics',    color: 'green',    shortcut: 'Ctrl+A',    action: 'analytics',  },  {    icon: Download,    label: 'Export Data',    color: 'purple',    shortcut: 'Ctrl+E',    action: 'export',  },  {    icon: Settings,    label: 'System Settings',    color: 'orange',    shortcut: 'Ctrl+S',    action: 'settings',  },];export const QuickActions = memo(  ({ onAddUser, onExport }: QuickActionsProps) => {    const handleAction = (action: string) => {      switch (action) {        case 'addUser':          onAddUser();          break;        case 'analytics':          console.log('Viewing analytics...');          break;        case 'export':          onExport();          break;        case 'settings':          console.log('Opening settings...');          break;      }    };    return (      <div className="border-border bg-card/40 rounded-xl border p-6">        <h3 className="mb-4 text-xl font-semibold">Quick Actions</h3>        <div className="space-y-3">          {actions.map((action, index) => {            const Icon = action.icon;            return (              <motion.div                key={action.label}                whileHover={{ scale: 1.02 }}                whileTap={{ scale: 0.98 }}              >                <Button                  variant="outline"                  className={`h-12 w-full justify-start hover:bg-${action.color}-500/10 hover:border-${action.color}-500/50 transition-all duration-200`}                  onClick={() => handleAction(action.action)}                >                  <Icon className={`mr-3 h-5 w-5 text-${action.color}-500`} />                  <span className="font-medium">{action.label}</span>                  <div className="text-muted-foreground ml-auto text-xs">                    {action.shortcut}                  </div>                </Button>              </motion.div>            );          })}        </div>      </div>    );  },);QuickActions.displayName = 'QuickActions';

Add recent-activity.tsx:

'use client';import { memo } from 'react';import { motion } from 'framer-motion';import { User, Download, Settings, Users } from 'lucide-react';const activities = [  {    action: 'User login',    user: 'john@example.com',    time: '2 min ago',    icon: User,    color: 'text-blue-500',  },  {    action: 'Data export',    user: 'admin',    time: '5 min ago',    icon: Download,    color: 'text-green-500',  },  {    action: 'Settings updated',    user: 'admin',    time: '10 min ago',    icon: Settings,    color: 'text-orange-500',  },  {    action: 'New user registered',    user: 'sarah@example.com',    time: '15 min ago',    icon: Users,    color: 'text-purple-500',  },];export const RecentActivity = memo(() => {  return (    <div className="border-border bg-card/40 rounded-xl border p-6">      <h3 className="mb-4 text-xl font-semibold">Recent Activity</h3>      <div className="space-y-3">        {activities.map((activity, index) => {          const Icon = activity.icon;          return (            <motion.div              key={index}              initial={{ opacity: 0, y: 10 }}              animate={{ opacity: 1, y: 0 }}              transition={{ delay: index * 0.1 }}              className="hover:bg-accent/50 flex items-center gap-3 rounded-lg p-2 transition-colors"            >              <div className={`bg-accent/50 rounded-lg p-2`}>                <Icon className={`h-4 w-4 ${activity.color}`} />              </div>              <div className="min-w-0 flex-1">                <div className="text-sm font-medium">{activity.action}</div>                <div className="text-muted-foreground truncate text-xs">                  {activity.user}                </div>              </div>              <div className="text-muted-foreground text-xs">                {activity.time}              </div>            </motion.div>          );        })}      </div>    </div>  );});RecentActivity.displayName = 'RecentActivity';

Add revenue-chart.tsx:

'use client';import { memo } from 'react';import { motion } from 'framer-motion';import { Button } from '@/components/ui/button';import { BarChart3, Calendar } from 'lucide-react';const chartData = [  { month: 'Jan', value: 4000, growth: 12, color: 'bg-blue-500' },  { month: 'Feb', value: 3000, growth: -8, color: 'bg-red-500' },  { month: 'Mar', value: 5000, growth: 25, color: 'bg-green-500' },  { month: 'Apr', value: 4500, growth: 15, color: 'bg-yellow-500' },  { month: 'May', value: 6000, growth: 33, color: 'bg-purple-500' },  { month: 'Jun', value: 5500, growth: 22, color: 'bg-cyan-500' },];export const RevenueChart = memo(() => {  return (    <div className="border-border bg-card/40 rounded-xl border p-6">      <div className="mb-6 flex items-center justify-between">        <div>          <h3 className="flex items-center gap-2 text-lg font-semibold">            <BarChart3 className="h-5 w-5 text-green-500" />            Revenue Analytics          </h3>          <p className="text-muted-foreground text-sm">            Monthly revenue performance          </p>        </div>        <Button variant="outline" size="sm">          <Calendar className="mr-2 h-4 w-4" />          Last 6 months        </Button>      </div>      {/* Fixed Chart Area */}      <div className="relative mb-4 h-64 rounded-lg p-4">        <div className="flex h-full items-end justify-between gap-3">          {chartData.map((item, index) => (            <div              key={item.month}              className="group flex flex-1 flex-col items-center"            >              <motion.div                initial={{ height: 0 }}                animate={{ height: `${(item.value / 6000) * 180}px` }}                transition={{ duration: 1, delay: index * 0.1 }}                className={`w-full ${item.color} relative min-h-[20px] cursor-pointer rounded-t-lg transition-opacity hover:opacity-80`}              >                {/* Tooltip */}                <div className="border-border bg-popover absolute -top-16 left-1/2 z-10 -translate-x-1/2 transform rounded-lg border px-3 py-2 text-sm whitespace-nowrap opacity-0 shadow-lg transition-opacity group-hover:opacity-100">                  <div className="font-medium">                    ${item.value.toLocaleString()}                  </div>                  <div                    className={`text-xs ${item.growth > 0 ? 'text-green-500' : 'text-red-500'}`}                  >                    {item.growth > 0 ? '+' : ''}                    {item.growth}%                  </div>                </div>              </motion.div>              <div className="text-muted-foreground mt-2 text-center text-xs font-medium">                {item.month}              </div>            </div>          ))}        </div>      </div>      {/* Summary Stats */}      <div className="border-border/50 grid grid-cols-3 gap-4 border-t pt-4">        <div className="text-center">          <div className="text-2xl font-bold text-green-500">$27K</div>          <div className="text-muted-foreground text-xs">Total Revenue</div>        </div>        <div className="text-center">          <div className="text-2xl font-bold text-blue-500">+18%</div>          <div className="text-muted-foreground text-xs">Growth Rate</div>        </div>        <div className="text-center">          <div className="text-2xl font-bold text-purple-500">$4.5K</div>          <div className="text-muted-foreground text-xs">Average</div>        </div>      </div>    </div>  );});RevenueChart.displayName = 'RevenueChart';

Add system-status.tsx:

'use client';import { memo } from 'react';import { motion } from 'framer-motion';import { Shield, Database, Zap, Activity } from 'lucide-react';const statusItems = [  {    label: 'Server Status',    status: 'Online',    color: 'text-green-500',    icon: Shield,    percentage: 100,  },  {    label: 'Database',    status: 'Healthy',    color: 'text-green-500',    icon: Database,    percentage: 95,  },  {    label: 'API Response',    status: 'Fast',    color: 'text-green-500',    icon: Zap,    percentage: 98,  },  {    label: 'Storage',    status: '85% Used',    color: 'text-yellow-500',    icon: Activity,    percentage: 85,  },];export const SystemStatus = memo(() => {  return (    <div className="border-border bg-card/40 rounded-xl border p-6">      <h3 className="mb-4 text-xl font-semibold">System Status</h3>      <div className="space-y-4">        {statusItems.map((item, index) => {          const Icon = item.icon;          return (            <motion.div              key={item.label}              initial={{ opacity: 0, x: 20 }}              animate={{ opacity: 1, x: 0 }}              transition={{ delay: index * 0.1 }}              className="hover:bg-accent/50 flex cursor-pointer items-center justify-between rounded-lg p-3 transition-colors"            >              <div className="flex items-center gap-3">                <Icon className={`h-4 w-4 ${item.color}`} />                <span className="text-sm font-medium">{item.label}</span>              </div>              <div className="flex items-center gap-3">                <div className="bg-muted h-2 w-16 overflow-hidden rounded-full">                  <motion.div                    initial={{ width: 0 }}                    animate={{ width: `${item.percentage}%` }}                    transition={{ duration: 1, delay: index * 0.1 }}                    className={`h-full rounded-full ${item.color.replace('text-', 'bg-')}`}                  />                </div>                <span                  className={`text-sm font-medium ${item.color} min-w-[60px] text-right`}                >                  {item.status}                </span>              </div>            </motion.div>          );        })}      </div>    </div>  );});SystemStatus.displayName = 'SystemStatus';

Add user-table.tsx:

'use client';import { memo } from 'react';import { motion } from 'framer-motion';import { Button } from '@/components/ui/button';import {  TrendingUp,  Plus,  Calendar,  Mail,  MapPin,  MoreHorizontal,} from 'lucide-react';const users = [  {    id: 1,    name: 'Alex Johnson',    email: 'alex@example.com',    avatar: 'https://i.pravatar.cc',    role: 'Admin',    status: 'active',    joinDate: '2024-01-15',    location: 'New York, US',  },  {    id: 2,    name: 'Sarah Chen',    email: 'sarah@example.com',    avatar: 'https://i.pravatar.cc',    role: 'User',    status: 'active',    joinDate: '2024-02-20',    location: 'San Francisco, US',  },  {    id: 3,    name: 'Michael Brown',    email: 'michael@example.com',    avatar: 'https://i.pravatar.cc',    role: 'Moderator',    status: 'inactive',    joinDate: '2024-01-08',    location: 'London, UK',  },];interface UsersTableProps {  onAddUser: () => void;}export const UsersTable = memo(({ onAddUser }: UsersTableProps) => {  return (    <div className="border-border bg-card/40 rounded-xl border p-3 sm:p-6">      <div className="mb-6 flex flex-col justify-between gap-4 sm:flex-row sm:items-center">        <div>          <h3 className="text-lg font-semibold sm:text-xl">Recent Users</h3>          <p className="text-muted-foreground text-sm">            Latest user registrations and activity          </p>        </div>        <div className="flex items-center gap-2">          <div className="flex items-center gap-1 text-sm text-green-500">            <TrendingUp className="h-4 w-4" />            <span>+12%</span>          </div>          <Button variant="outline" size="sm" onClick={onAddUser}>            <Plus className="mr-2 h-4 w-4" />            <span className="hidden sm:inline">Add User</span>            <span className="sm:hidden">Add</span>          </Button>        </div>      </div>      <div className="space-y-2">        {users.map((user, index) => (          <motion.div            key={user.id}            initial={{ opacity: 0, y: 10 }}            animate={{ opacity: 1, y: 0 }}            transition={{ delay: index * 0.05 }}            className="group hover:bg-accent/50 flex flex-col items-start gap-4 rounded-lg p-4 transition-colors sm:flex-row sm:items-center"          >            <div className="flex w-full items-center gap-4 sm:w-auto">              <div className="relative">                <img                  src={user.avatar}                  alt={user.name}                  width={40}                  height={40}                  className="rounded-full"                />                <div                  className={`border-background absolute -right-1 -bottom-1 h-3 w-3 rounded-full border-2 ${                    user.status === 'active' ? 'bg-green-500' : 'bg-red-500'                  }`}                />              </div>              <div className="min-w-0 flex-1">                <div className="flex flex-wrap items-center gap-2">                  <h4 className="truncate text-sm font-medium">{user.name}</h4>                  <span                    className={`rounded-full px-2 py-1 text-xs font-medium ${                      user.role === 'Admin'                        ? 'bg-purple-500/10 text-purple-500'                        : user.role === 'Moderator'                          ? 'bg-blue-500/10 text-blue-500'                          : 'bg-gray-500/10 text-gray-500'                    }`}                  >                    {user.role}                  </span>                </div>                <div className="text-muted-foreground mt-1 flex flex-col gap-2 text-xs sm:flex-row sm:items-center sm:gap-4">                  <div className="flex items-center gap-1">                    <Mail className="h-3 w-3" />                    <span className="truncate">{user.email}</span>                  </div>                  <div className="flex items-center gap-1">                    <MapPin className="h-3 w-3" />                    <span>{user.location}</span>                  </div>                </div>              </div>            </div>            <div className="ml-auto flex items-center gap-3">              <div className="text-muted-foreground flex items-center gap-1 text-xs">                <Calendar className="h-3 w-3" />                <span>{new Date(user.joinDate).toLocaleDateString()}</span>              </div>              <Button variant="ghost" size="sm" className="ml-auto">                <MoreHorizontal className="h-4 w-4" />              </Button>            </div>          </motion.div>        ))}      </div>    </div>  );});UsersTable.displayName = 'UsersTable';