theme-provider #16

Merged
andika merged 13 commits from theme-provider into dev 2026-01-16 10:40:04 +00:00
3 changed files with 71 additions and 24 deletions
Showing only changes of commit a93e2aa863 - Show all commits

View File

@@ -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>

View File

@@ -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>
<ThemeProvider
attribute="data-theme"
defaultTheme="light"
enableSystem={false}
storageKey="admin-theme"
>
<App /> <App />
</ThemeProvider>
</React.StrictMode> </React.StrictMode>
); );

View File

@@ -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}