theme-provider #16
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import api from '../utils/api';
|
import api from '../utils/api';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from './ui/badge';
|
||||||
@@ -23,16 +24,20 @@ import {
|
|||||||
HardDrive,
|
HardDrive,
|
||||||
Repeat,
|
Repeat,
|
||||||
Heart,
|
Heart,
|
||||||
|
Sun,
|
||||||
|
Moon,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
const [pendingCount, setPendingCount] = useState(0);
|
const [pendingCount, setPendingCount] = useState(0);
|
||||||
const [storageUsed, setStorageUsed] = useState(0);
|
const [storageUsed, setStorageUsed] = useState(0);
|
||||||
const [storageLimit, setStorageLimit] = useState(0);
|
const [storageLimit, setStorageLimit] = useState(0);
|
||||||
const [storagePercentage, setStoragePercentage] = useState(0);
|
const [storagePercentage, setStoragePercentage] = useState(0);
|
||||||
|
const isDark = theme === 'dark';
|
||||||
|
|
||||||
// Fetch pending approvals count
|
// Fetch pending approvals count
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -86,6 +91,10 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
navigate('/login');
|
navigate('/login');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleThemeToggle = () => {
|
||||||
|
setTheme(isDark ? 'light' : 'dark');
|
||||||
|
};
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{
|
{
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
@@ -211,7 +220,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
>
|
>
|
||||||
{/* Active border */}
|
{/* Active border */}
|
||||||
{active && !item.disabled && (
|
{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" />
|
<Icon className="h-5 w-5 flex-shrink-0" />
|
||||||
@@ -225,7 +234,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{item.badge > 0 && !item.disabled && (
|
{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}
|
{item.badge}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
@@ -234,7 +243,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
|
|
||||||
{/* Badge when collapsed */}
|
{/* Badge when collapsed */}
|
||||||
{!isOpen && item.badge > 0 && !item.disabled && (
|
{!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}
|
{item.badge}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -242,7 +251,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
|
|
||||||
{/* Tooltip when collapsed */}
|
{/* Tooltip when collapsed */}
|
||||||
{!isOpen && (
|
{!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.name}
|
||||||
{item.badge > 0 && ` (${item.badge})`}
|
{item.badge > 0 && ` (${item.badge})`}
|
||||||
</div>
|
</div>
|
||||||
@@ -274,10 +283,10 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
/>
|
/>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="flex-1 min-w-0">
|
<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
|
Admin
|
||||||
</h2>
|
</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
|
View Public Site
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -289,11 +298,11 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
aria-label={isOpen ? 'Collapse sidebar' : 'Expand sidebar'}
|
aria-label={isOpen ? 'Collapse sidebar' : 'Expand sidebar'}
|
||||||
>
|
>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<Menu className="h-5 w-5 text-[#422268]" />
|
<Menu className="h-5 w-5 text-primary" />
|
||||||
) : isOpen ? (
|
) : 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -306,7 +315,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
{/* MEMBERSHIP Section */}
|
{/* MEMBERSHIP Section */}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="px-4 py-2 mt-6">
|
<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
|
Membership
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -320,7 +329,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
{/* FINANCIALS Section */}
|
{/* FINANCIALS Section */}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="px-4 py-2 mt-6">
|
<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
|
Financials
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -334,7 +343,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
{/* EVENTS & MEDIA Section */}
|
{/* EVENTS & MEDIA Section */}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="px-4 py-2 mt-6">
|
<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
|
Events & Media
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -347,7 +356,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
{/* DOCUMENTATION Section */}
|
{/* DOCUMENTATION Section */}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="px-4 py-2 mt-6">
|
<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
|
Documentation
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -375,10 +384,10 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
{user.first_name?.[0]}{user.last_name?.[0]}
|
{user.first_name?.[0]}{user.last_name?.[0]}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<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}
|
{user.first_name} {user.last_name}
|
||||||
</p>
|
</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}
|
{user.role}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -388,13 +397,40 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
</div>
|
</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 */}
|
{/* Storage Usage Widget */}
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<div className="px-4 py-3 bg-[#F8F7FB] rounded-lg">
|
<div className="px-4 py-3 bg-[#F8F7FB] rounded-lg">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm font-medium text-[#422268]">Storage Usage</span>
|
<span className="text-sm font-medium text-primary">Storage Usage</span>
|
||||||
<span className="text-xs text-[#664fa3]">{storagePercentage}%</span>
|
<span className="text-xs text-muted-foreground">{storagePercentage}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-[#ddd8eb] rounded-full h-2">
|
<div className="w-full bg-[#ddd8eb] rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
@@ -405,7 +441,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
style={{ width: `${storagePercentage}%` }}
|
style={{ width: `${storagePercentage}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-[#664fa3] mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
{formatBytes(storageUsed)} / {formatBytes(storageLimit)}
|
{formatBytes(storageUsed)} / {formatBytes(storageLimit)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -414,13 +450,13 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<HardDrive className={`h-5 w-5 ${storagePercentage > 90 ? 'text-red-500' :
|
<HardDrive className={`h-5 w-5 ${storagePercentage > 90 ? 'text-red-500' :
|
||||||
storagePercentage > 75 ? 'text-yellow-500' :
|
storagePercentage > 75 ? 'text-yellow-500' :
|
||||||
'text-[#664fa3]'
|
'text-muted-foreground'
|
||||||
}`} />
|
}`} />
|
||||||
{storagePercentage > 75 && (
|
{storagePercentage > 75 && (
|
||||||
<div className="absolute -top-1 -right-1 bg-red-500 h-2 w-2 rounded-full" />
|
<div className="absolute -top-1 -right-1 bg-red-500 h-2 w-2 rounded-full" />
|
||||||
)}
|
)}
|
||||||
{/* Tooltip */}
|
{/* 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}%
|
Storage: {storagePercentage}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -433,7 +469,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className={`
|
className={`
|
||||||
flex items-center gap-3 px-4 py-3 rounded-lg w-full
|
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'}
|
${!isOpen && 'justify-center'}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
@@ -444,7 +480,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
{/* Logout tooltip when collapsed */}
|
{/* Logout tooltip when collapsed */}
|
||||||
{!isOpen && (
|
{!isOpen && (
|
||||||
<div className="relative group">
|
<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
|
Logout
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
10
src/index.js
10
src/index.js
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { ThemeProvider } from 'next-themes';
|
||||||
import '@fontsource/fraunces/600.css';
|
import '@fontsource/fraunces/600.css';
|
||||||
import '@fontsource/dm-sans/400.css';
|
import '@fontsource/dm-sans/400.css';
|
||||||
import '@fontsource/dm-sans/700.css';
|
import '@fontsource/dm-sans/700.css';
|
||||||
@@ -9,6 +10,13 @@ import App from './App';
|
|||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<ThemeProvider
|
||||||
|
attribute="data-theme"
|
||||||
|
defaultTheme="light"
|
||||||
|
enableSystem={false}
|
||||||
|
storageKey="admin-theme"
|
||||||
|
>
|
||||||
|
<App />
|
||||||
|
</ThemeProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
import AdminSidebar from '../components/AdminSidebar';
|
import AdminSidebar from '../components/AdminSidebar';
|
||||||
|
|
||||||
const AdminLayout = ({ children }) => {
|
const AdminLayout = ({ children }) => {
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const isDark = theme === 'dark';
|
||||||
|
|
||||||
// Initialize sidebar state from localStorage
|
// Initialize sidebar state from localStorage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -43,7 +46,7 @@ const AdminLayout = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen bg-white">
|
<div className={`flex h-screen bg-background ${isDark ? 'dark' : ''}`}>
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<AdminSidebar
|
<AdminSidebar
|
||||||
isOpen={sidebarOpen}
|
isOpen={sidebarOpen}
|
||||||
|
|||||||
Reference in New Issue
Block a user