applied dark mode
This commit is contained in:
parent
abf12c8f68
commit
a7b6b78890
21
package-lock.json
generated
21
package-lock.json
generated
@ -12,9 +12,11 @@
|
||||
"appwrite": "^17.0.1",
|
||||
"lucide-react": "^0.487.0",
|
||||
"next": "15.2.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"postcss": "^8.5.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"tailwindcss": "^4.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -4311,6 +4313,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-themes": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
||||
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
@ -4701,6 +4713,15 @@
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
@ -13,9 +13,11 @@
|
||||
"appwrite": "^17.0.1",
|
||||
"lucide-react": "^0.487.0",
|
||||
"next": "15.2.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"postcss": "^8.5.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"tailwindcss": "^4.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,5 +1,6 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
darkMode: ['class', '[data-theme="dark"]'],
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
79
src/app/client-layout.js
Normal file
79
src/app/client-layout.js
Normal file
@ -0,0 +1,79 @@
|
||||
'use client';
|
||||
import { useState, useEffect } from "react";
|
||||
import { account } from "./lib/appwrite";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Sidebar from "./components/Sidebar";
|
||||
import Navbar from "./components/Navbar";
|
||||
import { Menu } from 'lucide-react';
|
||||
|
||||
export default function ClientLayout({ children }) {
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
|
||||
const [isMobileOpen, setIsMobileOpen] = useState(false);
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const checkAuth = async () => {
|
||||
try {
|
||||
await account.get();
|
||||
setIsLoggedIn(true);
|
||||
} catch (error) {
|
||||
setIsLoggedIn(false);
|
||||
router.push('/login');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
checkAuth();
|
||||
}, [router]);
|
||||
|
||||
const toggleMobileSidebar = () => {
|
||||
setIsMobileOpen(!isMobileOpen);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className="flex items-center justify-center min-h-screen">Loading...</div>;
|
||||
}
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<main className="p-4 md:p-6">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen">
|
||||
{/* Mobile Toggle Button (outside sidebar) */}
|
||||
<button
|
||||
onClick={toggleMobileSidebar}
|
||||
className="md:hidden fixed top-4 left-4 z-50 bg-white dark:bg-gray-800 p-2 rounded-md shadow-md"
|
||||
>
|
||||
<Menu size={24} />
|
||||
</button>
|
||||
|
||||
<Sidebar
|
||||
isCollapsed={isSidebarCollapsed}
|
||||
isMobileOpen={isMobileOpen}
|
||||
toggleMobileSidebar={toggleMobileSidebar}
|
||||
/>
|
||||
|
||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${
|
||||
isSidebarCollapsed ? 'md:pl-20' : 'md:pl-64'
|
||||
}`}>
|
||||
<Navbar
|
||||
isSidebarCollapsed={isSidebarCollapsed}
|
||||
toggleSidebar={() => setIsSidebarCollapsed(!isSidebarCollapsed)}
|
||||
/>
|
||||
|
||||
<main className="flex-1 p-4 md:p-6 overflow-auto">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,34 +1,126 @@
|
||||
'use client';
|
||||
import { useState, useEffect } from "react";
|
||||
import { account } from "../lib/appwrite";
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { LogOut } from 'lucide-react';
|
||||
import { useRouter } from "next/navigation";
|
||||
import { FaUserCircle, FaMoon, FaSun } from "react-icons/fa";
|
||||
import { LogOut, Menu } from "lucide-react";
|
||||
|
||||
export default function Navbar() {
|
||||
export default function Navbar({ toggleSidebar }) {
|
||||
const router = useRouter();
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const [user, setUser] = useState(null);
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
|
||||
// Initialize dark mode from localStorage or system preference
|
||||
useEffect(() => {
|
||||
const savedMode = localStorage.getItem('darkMode');
|
||||
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
if (savedMode !== null) {
|
||||
setDarkMode(savedMode === 'true');
|
||||
} else if (systemPrefersDark) {
|
||||
setDarkMode(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Apply dark mode class to document element
|
||||
useEffect(() => {
|
||||
if (darkMode) {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('darkMode', 'true');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('darkMode', 'false');
|
||||
}
|
||||
}, [darkMode]);
|
||||
|
||||
// Toggle dark mode
|
||||
const toggleDarkMode = () => {
|
||||
setDarkMode(!darkMode);
|
||||
};
|
||||
|
||||
// Fetch current user details
|
||||
useEffect(() => {
|
||||
const getUser = async () => {
|
||||
try {
|
||||
const userData = await account.get();
|
||||
setUser(userData);
|
||||
} catch (error) {
|
||||
console.error("Error fetching user:", error);
|
||||
}
|
||||
};
|
||||
getUser();
|
||||
}, []);
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await account.deleteSession('current');
|
||||
router.push('/login');
|
||||
await account.deleteSession("current");
|
||||
router.push("/login");
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
console.error("Logout failed:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="bg-white border-b border-gray-200 px-4 py-3 sm:px-6 lg:px-8">
|
||||
<nav className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 py-3 sm:px-6 lg:px-8 relative">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<span className="font-semibold text-gray-800">Company Portal</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Sidebar Toggle Button - Hidden on mobile */}
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="flex items-center space-x-1 text-red-600 hover:text-red-800 transition-colors"
|
||||
onClick={toggleSidebar}
|
||||
className="hidden md:block p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
>
|
||||
<LogOut className="w-5 h-5" />
|
||||
<span className="hidden sm:inline">Logout</span>
|
||||
<Menu size={24} className="text-gray-800 dark:text-gray-200" />
|
||||
</button>
|
||||
<span className="font-semibold text-gray-800 dark:text-gray-200 text-lg">
|
||||
FSPL HR Portal
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Dark Mode Toggle */}
|
||||
<button
|
||||
onClick={toggleDarkMode}
|
||||
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none"
|
||||
aria-label={darkMode ? "Switch to light mode" : "Switch to dark mode"}
|
||||
>
|
||||
{darkMode ? (
|
||||
<FaSun className="text-yellow-400 text-xl" />
|
||||
) : (
|
||||
<FaMoon className="text-gray-700 dark:text-gray-200 text-xl" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* User Dropdown */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
||||
className="flex items-center focus:outline-none"
|
||||
>
|
||||
<FaUserCircle className="text-3xl text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400" />
|
||||
</button>
|
||||
|
||||
{isDropdownOpen && (
|
||||
<div className="absolute right-0 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50">
|
||||
<div className="p-4">
|
||||
<p className="font-semibold text-gray-800 dark:text-gray-200">
|
||||
{user?.name || "Guest"}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 truncate">
|
||||
{user?.email || "No email"}
|
||||
</p>
|
||||
</div>
|
||||
<hr className="border-gray-200 dark:border-gray-700" />
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="w-full text-left px-4 py-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-gray-700 flex items-center space-x-2"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
<span>Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -1,27 +1,62 @@
|
||||
'use client';
|
||||
import Link from 'next/link';
|
||||
import { X, Home, Users, ClipboardList, User } from 'lucide-react';
|
||||
|
||||
export default function Sidebar({ isCollapsed, isMobileOpen, toggleMobileSidebar }) {
|
||||
const navItems = [
|
||||
{ href: "/pages/dashboard", icon: Home, label: "Home" },
|
||||
{ href: "/pages/user", icon: Users, label: "User" },
|
||||
{ href: "/pages/admin/members", icon: Users, label: "Members" },
|
||||
{ href: "/pages/assignments", icon: ClipboardList, label: "Assignments" },
|
||||
{ href: "/pages/user/profile", icon: User, label: "My Profile" },
|
||||
];
|
||||
|
||||
export default function Sidebar() {
|
||||
return (
|
||||
<aside className="w-64 border-r border-gray-200 h-screen bg-white-800 text-gray-700 p-4 hidden md:block">
|
||||
<h2 className="text-xl font-bold mb-6">Dashboard</h2>
|
||||
<ul className="space-y-4">
|
||||
<li>
|
||||
<Link href="/pages/dashboard" className="hover:text-blue-500">Home</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/pages/user" className="hover:text-blue-500">user</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/pages/admin/members" className="hover:text-blue-500">Members</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/pages/assignments" className="hover:text-blue-500">Assignments</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/pages/user/profile" className="hover:text-blue-500">My Profile</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<aside className={`
|
||||
fixed top-0 left-0 h-full bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 z-40
|
||||
transition-all duration-300
|
||||
${isCollapsed ? 'w-20' : 'w-64'}
|
||||
${isMobileOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}
|
||||
`}>
|
||||
<div className="p-4 h-full flex flex-col">
|
||||
{/* Mobile Close Button (visible only on mobile) */}
|
||||
<button
|
||||
onClick={toggleMobileSidebar}
|
||||
className="md:hidden absolute top-4 right-4 p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<div className={`flex items-center ${isCollapsed ? 'justify-center' : 'justify-between'} mb-6`}>
|
||||
{!isCollapsed && (
|
||||
<h2 className="text-2xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-purple-500">
|
||||
HrMaTe
|
||||
</h2>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<nav className="flex-1">
|
||||
<ul className="space-y-2">
|
||||
{navItems.map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={`
|
||||
flex items-center p-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700
|
||||
${isCollapsed ? 'justify-center' : 'px-4'}
|
||||
`}
|
||||
onClick={toggleMobileSidebar} // Close sidebar when clicking a link on mobile
|
||||
>
|
||||
<item.icon className="w-5 h-5" />
|
||||
{!isCollapsed && (
|
||||
<span className="ml-3">{item.label}</span>
|
||||
)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
}
|
10
src/app/context/ThemeContext.js
Normal file
10
src/app/context/ThemeContext.js
Normal file
@ -0,0 +1,10 @@
|
||||
'use client';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
|
||||
export function Providers({ children }) {
|
||||
return (
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
@ -1 +1,33 @@
|
||||
@import "tailwindcss";
|
||||
@import "tailwindcss";
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
/* globals.css */
|
||||
html {
|
||||
transition: color-scheme 0.3s ease;
|
||||
}
|
||||
body {
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #111827;
|
||||
/* other light mode variables */
|
||||
}
|
||||
|
||||
.dark {
|
||||
--bg-color: #111827;
|
||||
--text-color: #f3f4f6;
|
||||
/* other dark mode variables */
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Sidebar from "./components/Sidebar"
|
||||
import Navbar from "./components/Navbar";
|
||||
import ClientLayout from "./client-layout";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@ -20,16 +19,10 @@ export const metadata = {
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${geistSans.variable} ${geistMono.variable}`}>
|
||||
<div className="flex">
|
||||
<Sidebar />
|
||||
<div className="flex-1">
|
||||
<Navbar />
|
||||
<main className="p-4">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
<html lang="en" className={`${geistSans.variable} ${geistMono.variable}`}>
|
||||
<body className="bg-gray-50">
|
||||
<ClientLayout>{children}</ClientLayout>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user