|
|
|
|
@@ -1,5 +1,6 @@
|
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
|
|
|
|
import { useTheme } from 'next-themes';
|
|
|
|
|
import { useAuth } from '../context/AuthContext';
|
|
|
|
|
import api from '../utils/api';
|
|
|
|
|
import { Badge } from './ui/badge';
|
|
|
|
|
@@ -23,16 +24,20 @@ import {
|
|
|
|
|
HardDrive,
|
|
|
|
|
Repeat,
|
|
|
|
|
Heart,
|
|
|
|
|
Sun,
|
|
|
|
|
Moon,
|
|
|
|
|
} from 'lucide-react';
|
|
|
|
|
|
|
|
|
|
const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
const { user, logout } = useAuth();
|
|
|
|
|
const { theme, setTheme } = useTheme();
|
|
|
|
|
const [pendingCount, setPendingCount] = useState(0);
|
|
|
|
|
const [storageUsed, setStorageUsed] = useState(0);
|
|
|
|
|
const [storageLimit, setStorageLimit] = useState(0);
|
|
|
|
|
const [storagePercentage, setStoragePercentage] = useState(0);
|
|
|
|
|
const isDark = theme === 'dark';
|
|
|
|
|
|
|
|
|
|
// Fetch pending approvals count
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
@@ -86,6 +91,10 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
navigate('/login');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleThemeToggle = () => {
|
|
|
|
|
setTheme(isDark ? 'light' : 'dark');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const navItems = [
|
|
|
|
|
{
|
|
|
|
|
name: 'Dashboard',
|
|
|
|
|
@@ -211,7 +220,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
>
|
|
|
|
|
{/* Active border */}
|
|
|
|
|
{active && !item.disabled && (
|
|
|
|
|
<div className="absolute left-0 top-0 bottom-0 w-1 bg-[#ff9e77] rounded-r" />
|
|
|
|
|
<div className="absolute left-0 top-0 bottom-0 w-1 bg-accent rounded-r" />
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Icon className="h-5 w-5 flex-shrink-0" />
|
|
|
|
|
@@ -225,7 +234,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
{item.badge > 0 && !item.disabled && (
|
|
|
|
|
<Badge className="bg-[#ff9e77] text-white text-xs px-2 py-0.5">
|
|
|
|
|
<Badge className="bg-accent foreground text-xs px-2 py-0.5">
|
|
|
|
|
{item.badge}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
@@ -234,7 +243,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
|
|
|
|
|
{/* Badge when collapsed */}
|
|
|
|
|
{!isOpen && item.badge > 0 && !item.disabled && (
|
|
|
|
|
<div className="absolute -top-1 -right-1 bg-[#ff9e77] text-white text-xs rounded-full h-5 w-5 flex items-center justify-center font-medium">
|
|
|
|
|
<div className="absolute -top-1 -right-1 bg-accent foreground text-xs rounded-full h-5 w-5 flex items-center justify-center font-medium">
|
|
|
|
|
{item.badge}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
@@ -242,7 +251,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
|
|
|
|
|
{/* Tooltip when collapsed */}
|
|
|
|
|
{!isOpen && (
|
|
|
|
|
<div className="absolute left-full ml-2 top-1/2 -translate-y-1/2 px-3 py-2 bg-[#422268] text-white text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
|
|
|
|
<div className="absolute left-full ml-2 top-1/2 -translate-y-1/2 px-3 py-2 bg-primary foreground text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
|
|
|
|
{item.name}
|
|
|
|
|
{item.badge > 0 && ` (${item.badge})`}
|
|
|
|
|
</div>
|
|
|
|
|
@@ -274,10 +283,10 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
/>
|
|
|
|
|
{isOpen && (
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<h2 className="text-xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
|
|
|
|
<h2 className="text-xl font-semibold text-primary" style={{ fontFamily: "'Inter', sans-serif" }}>
|
|
|
|
|
Admin
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-xs text-[#664fa3] group-hover:text-[#ff9e77] transition-colors" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
|
|
|
|
<p className="text-xs text-muted-foreground group-hover:text-accent transition-colors" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
|
|
|
|
View Public Site
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -289,11 +298,11 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
aria-label={isOpen ? 'Collapse sidebar' : 'Expand sidebar'}
|
|
|
|
|
>
|
|
|
|
|
{isMobile ? (
|
|
|
|
|
<Menu className="h-5 w-5 text-[#422268]" />
|
|
|
|
|
<Menu className="h-5 w-5 text-primary" />
|
|
|
|
|
) : isOpen ? (
|
|
|
|
|
<ChevronLeft className="h-5 w-5 text-[#422268]" />
|
|
|
|
|
<ChevronLeft className="h-5 w-5 text-primary" />
|
|
|
|
|
) : (
|
|
|
|
|
<ChevronRight className="h-5 w-5 text-[#422268]" />
|
|
|
|
|
<ChevronRight className="h-5 w-5 text-primary" />
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -306,7 +315,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
{/* MEMBERSHIP Section */}
|
|
|
|
|
{isOpen && (
|
|
|
|
|
<div className="px-4 py-2 mt-6">
|
|
|
|
|
<h3 className="text-xs font-semibold text-[#664fa3] uppercase tracking-wider">
|
|
|
|
|
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
|
|
|
Membership
|
|
|
|
|
</h3>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -320,7 +329,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
{/* FINANCIALS Section */}
|
|
|
|
|
{isOpen && (
|
|
|
|
|
<div className="px-4 py-2 mt-6">
|
|
|
|
|
<h3 className="text-xs font-semibold text-[#664fa3] uppercase tracking-wider">
|
|
|
|
|
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
|
|
|
Financials
|
|
|
|
|
</h3>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -334,7 +343,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
{/* EVENTS & MEDIA Section */}
|
|
|
|
|
{isOpen && (
|
|
|
|
|
<div className="px-4 py-2 mt-6">
|
|
|
|
|
<h3 className="text-xs font-semibold text-[#664fa3] uppercase tracking-wider">
|
|
|
|
|
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
|
|
|
Events & Media
|
|
|
|
|
</h3>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -347,7 +356,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
{/* DOCUMENTATION Section */}
|
|
|
|
|
{isOpen && (
|
|
|
|
|
<div className="px-4 py-2 mt-6">
|
|
|
|
|
<h3 className="text-xs font-semibold text-[#664fa3] uppercase tracking-wider">
|
|
|
|
|
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
|
|
|
Documentation
|
|
|
|
|
</h3>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -375,10 +384,10 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
{user.first_name?.[0]}{user.last_name?.[0]}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<p className="text-sm font-medium text-[#422268] truncate" style={{ fontFamily: "'Inter', sans-serif" }}>
|
|
|
|
|
<p className="text-sm font-medium text-primary truncate" style={{ fontFamily: "'Inter', sans-serif" }}>
|
|
|
|
|
{user.first_name} {user.last_name}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-xs text-[#664fa3] capitalize truncate" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
|
|
|
|
<p className="text-xs text-muted-foreground capitalize truncate" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
|
|
|
|
{user.role}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -388,13 +397,40 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Theme Toggle */}
|
|
|
|
|
<div className="relative group">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={handleThemeToggle}
|
|
|
|
|
aria-pressed={isDark}
|
|
|
|
|
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
|
|
|
|
|
className={`
|
|
|
|
|
flex items-center gap-3 px-4 py-3 rounded-lg w-full
|
|
|
|
|
text-primary hover:bg-muted/20 transition-colors
|
|
|
|
|
${!isOpen && 'justify-center'}
|
|
|
|
|
`}
|
|
|
|
|
>
|
|
|
|
|
{isDark ? (
|
|
|
|
|
<Sun className="h-5 w-5 flex-shrink-0" />
|
|
|
|
|
) : (
|
|
|
|
|
<Moon className="h-5 w-5 flex-shrink-0" />
|
|
|
|
|
)}
|
|
|
|
|
{isOpen && <span>{isDark ? 'Light mode' : 'Dark mode'}</span>}
|
|
|
|
|
</button>
|
|
|
|
|
{!isOpen && (
|
|
|
|
|
<div className="absolute left-full ml-2 top-1/2 -translate-y-1/2 px-3 py-2 bg-primary foreground text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
|
|
|
|
{isDark ? 'Light mode' : 'Dark mode'}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Storage Usage Widget */}
|
|
|
|
|
<div className="mb-2">
|
|
|
|
|
{isOpen ? (
|
|
|
|
|
<div className="px-4 py-3 bg-[#F8F7FB] rounded-lg">
|
|
|
|
|
<div className="flex items-center justify-between mb-2">
|
|
|
|
|
<span className="text-sm font-medium text-[#422268]">Storage Usage</span>
|
|
|
|
|
<span className="text-xs text-[#664fa3]">{storagePercentage}%</span>
|
|
|
|
|
<span className="text-sm font-medium text-primary">Storage Usage</span>
|
|
|
|
|
<span className="text-xs text-muted-foreground">{storagePercentage}%</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="w-full bg-[#ddd8eb] rounded-full h-2">
|
|
|
|
|
<div
|
|
|
|
|
@@ -405,7 +441,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
style={{ width: `${storagePercentage}%` }}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-[#664fa3] mt-1">
|
|
|
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
|
|
|
{formatBytes(storageUsed)} / {formatBytes(storageLimit)}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -414,13 +450,13 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
<div className="relative group">
|
|
|
|
|
<HardDrive className={`h-5 w-5 ${storagePercentage > 90 ? 'text-red-500' :
|
|
|
|
|
storagePercentage > 75 ? 'text-yellow-500' :
|
|
|
|
|
'text-[#664fa3]'
|
|
|
|
|
'text-muted-foreground'
|
|
|
|
|
}`} />
|
|
|
|
|
{storagePercentage > 75 && (
|
|
|
|
|
<div className="absolute -top-1 -right-1 bg-red-500 h-2 w-2 rounded-full" />
|
|
|
|
|
)}
|
|
|
|
|
{/* Tooltip */}
|
|
|
|
|
<div className="absolute left-full ml-2 top-1/2 -translate-y-1/2 px-3 py-2 bg-[#422268] text-white text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
|
|
|
|
<div className="absolute left-full ml-2 top-1/2 -translate-y-1/2 px-3 py-2 bg-primary foreground text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
|
|
|
|
Storage: {storagePercentage}%
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -433,7 +469,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
onClick={handleLogout}
|
|
|
|
|
className={`
|
|
|
|
|
flex items-center gap-3 px-4 py-3 rounded-lg w-full
|
|
|
|
|
text-[#ff9e77] hover:bg-[#ff9e77]/10 transition-colors
|
|
|
|
|
text-accent hover:bg-accent/10 transition-colors
|
|
|
|
|
${!isOpen && 'justify-center'}
|
|
|
|
|
`}
|
|
|
|
|
>
|
|
|
|
|
@@ -444,7 +480,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|
|
|
|
{/* Logout tooltip when collapsed */}
|
|
|
|
|
{!isOpen && (
|
|
|
|
|
<div className="relative group">
|
|
|
|
|
<div className="absolute left-full ml-2 bottom-0 px-3 py-2 bg-[#422268] text-white text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
|
|
|
|
<div className="absolute left-full ml-2 bottom-0 px-3 py-2 bg-primary foreground text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
|
|
|
|
Logout
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|