Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions Frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,21 @@ function ProtectedRoute({ children }) {
}

function App() {
const { theme } = useStore();
const { theme, setTheme } = useStore();

useEffect(() => {
const root = window.document.documentElement;
root.classList.remove('light', 'dark');

if (theme === 'system' || theme === 'auto') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
root.classList.add(systemTheme);
return;
let effectiveTheme = theme;
if (theme !== 'light' && theme !== 'dark') {
effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
// Normalise the stored theme so future toggles are predictable
setTheme(effectiveTheme);
}

root.classList.add(theme);
}, [theme]);
root.classList.add(effectiveTheme);
}, [theme, setTheme]);

return (
<Router>
Expand Down
75 changes: 40 additions & 35 deletions Frontend/src/components/Layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {
LogOut,
Menu,
X,
Bell,
ChevronDown
ChevronDown,
Sun,
Moon
} from 'lucide-react';

export default function Layout({ children }) {
const { user, logout, notifications, unreadCount } = useStore();
const { user, logout, theme, setTheme } = useStore();
const navigate = useNavigate();
const location = useLocation();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
Expand All @@ -25,6 +26,11 @@ export default function Layout({ children }) {
navigate('/login');
};

const toggleTheme = () => {
// Simple, predictable toggle between light and dark
setTheme(theme === 'dark' ? 'light' : 'dark');
};

const navItems = [
{ icon: LayoutDashboard, label: 'Dashboard', path: '/' },
{ icon: Users, label: 'My Groups', path: '/groups' },
Expand All @@ -33,15 +39,15 @@ export default function Layout({ children }) {
];

return (
<div className="min-h-screen bg-gray-50 flex">
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex">
{/* Sidebar (Desktop) */}
<aside className="hidden md:flex flex-col w-64 bg-white border-r border-gray-100 fixed h-full z-30">
<div className="p-6 border-b border-gray-50">
<aside className="hidden md:flex flex-col w-64 bg-white dark:bg-gray-800 border-r border-gray-100 dark:border-gray-700 fixed h-full z-30">
<div className="p-6 border-b border-gray-50 dark:border-gray-700">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-primary rounded-xl flex items-center justify-center text-white font-bold text-xl shadow-lg shadow-indigo-200">
<div className="w-10 h-10 bg-primary rounded-xl flex items-center justify-center text-white font-bold text-xl shadow-lg shadow-indigo-200 dark:shadow-indigo-900/50">
E
</div>
<span className="font-bold text-xl text-gray-900">Expense Splitter</span>
<span className="font-bold text-xl text-gray-900 dark:text-gray-100">Expense Splitter</span>
</div>
</div>

Expand All @@ -53,8 +59,8 @@ export default function Layout({ children }) {
className={({ isActive }) => cn(
"flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-200 font-medium",
isActive
? "bg-primary-light/50 text-primary"
: "text-gray-500 hover:bg-gray-50 hover:text-gray-900"
? "bg-primary-light/50 dark:bg-primary/20 text-primary dark:text-primary-light"
: "text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100"
)}
>
<item.icon size={20} />
Expand All @@ -63,10 +69,10 @@ export default function Layout({ children }) {
))}
</nav>

<div className="p-4 border-t border-gray-50">
<div className="p-4 border-t border-gray-50 dark:border-gray-700">
<button
onClick={handleLogout}
className="flex items-center gap-3 px-4 py-3 w-full rounded-xl text-red-500 hover:bg-red-50 transition-all duration-200 font-medium"
className="flex items-center gap-3 px-4 py-3 w-full rounded-xl text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 transition-all duration-200 font-medium"
>
<LogOut size={20} />
Logout
Expand All @@ -84,13 +90,13 @@ export default function Layout({ children }) {

{/* Mobile Sidebar */}
<aside className={cn(
"fixed inset-y-0 left-0 w-64 bg-white z-50 transform transition-transform duration-300 ease-in-out md:hidden",
"fixed inset-y-0 left-0 w-64 bg-white dark:bg-gray-800 z-50 transform transition-transform duration-300 ease-in-out md:hidden",
isMobileMenuOpen ? "translate-x-0" : "-translate-x-full"
)}>
<div className="p-6 flex justify-between items-center border-b border-gray-50">
<span className="font-bold text-xl text-gray-900">Menu</span>
<div className="p-6 flex justify-between items-center border-b border-gray-50 dark:border-gray-700">
<span className="font-bold text-xl text-gray-900 dark:text-gray-100">Menu</span>
<button onClick={() => setIsMobileMenuOpen(false)}>
<X size={24} className="text-gray-500" />
<X size={24} className="text-gray-500 dark:text-gray-400" />
</button>
</div>
<nav className="p-4 space-y-2">
Expand All @@ -102,8 +108,8 @@ export default function Layout({ children }) {
className={({ isActive }) => cn(
"flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-200 font-medium",
isActive
? "bg-primary-light/50 text-primary"
: "text-gray-500 hover:bg-gray-50 hover:text-gray-900"
? "bg-primary-light/50 dark:bg-primary/20 text-primary dark:text-primary-light"
: "text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100"
)}
>
<item.icon size={20} />
Expand All @@ -112,7 +118,7 @@ export default function Layout({ children }) {
))}
<button
onClick={handleLogout}
className="flex items-center gap-3 px-4 py-3 w-full rounded-xl text-red-500 hover:bg-red-50 transition-all duration-200 font-medium mt-4"
className="flex items-center gap-3 px-4 py-3 w-full rounded-xl text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 transition-all duration-200 font-medium mt-4"
>
<LogOut size={20} />
Logout
Expand All @@ -123,43 +129,42 @@ export default function Layout({ children }) {
{/* Main Content */}
<main className="flex-1 md:ml-64 min-h-screen flex flex-col">
{/* Top Navbar */}
<header className="bg-white/80 backdrop-blur-md sticky top-0 z-20 border-b border-gray-100 px-6 py-4 flex justify-between items-center">
<header className="bg-white/80 dark:bg-gray-800/80 backdrop-blur-md sticky top-0 z-20 border-b border-gray-100 dark:border-gray-700 px-6 py-4 flex justify-between items-center">
<div className="flex items-center gap-4">
<button
onClick={() => setIsMobileMenuOpen(true)}
className="md:hidden p-2 text-gray-500 hover:bg-gray-50 rounded-lg"
className="md:hidden p-2 text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-lg"
>
<Menu size={24} />
</button>
<h2 className="text-xl font-bold text-gray-800 hidden sm:block">
<h2 className="text-xl font-bold text-gray-800 dark:text-gray-100 hidden sm:block">
{navItems.find(i => i.path === location.pathname)?.label || 'Dashboard'}
</h2>
</div>

<div className="flex items-center gap-6">
{/* Notifications */}
<div className="relative">
<button className="p-2 text-gray-400 hover:text-primary transition-colors rounded-full hover:bg-indigo-50 relative">
<Bell size={24} />
{unreadCount > 0 && (
<span className="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full border-2 border-white" />
)}
</button>
</div>
{/* Theme Toggle */}
<button
onClick={toggleTheme}
className="p-2 text-gray-400 dark:text-gray-500 hover:text-primary dark:hover:text-primary-light transition-colors rounded-full hover:bg-indigo-50 dark:hover:bg-gray-700"
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
>
{theme === 'dark' ? <Sun size={24} /> : <Moon size={24} />}
</button>

{/* User Profile */}
<div className="relative">
<button
onClick={() => setIsProfileOpen(!isProfileOpen)}
className="flex items-center gap-3 hover:bg-gray-50 p-2 rounded-xl transition-colors"
className="flex items-center gap-3 hover:bg-gray-50 dark:hover:bg-gray-700 p-2 rounded-xl transition-colors"
>
<div className="w-10 h-10 bg-gradient-to-br from-indigo-100 to-purple-100 rounded-full flex items-center justify-center text-primary font-bold">
<div className="w-10 h-10 bg-gradient-to-br from-indigo-100 to-purple-100 dark:from-indigo-900 dark:to-purple-900 rounded-full flex items-center justify-center text-primary dark:text-primary-light font-bold">
{user?.name?.charAt(0).toUpperCase()}
</div>
<div className="hidden md:block text-left">
<p className="text-sm font-semibold text-gray-900">{user?.name}</p>
<p className="text-sm font-semibold text-gray-900 dark:text-gray-100">{user?.name}</p>
</div>
<ChevronDown size={16} className="text-gray-400 hidden md:block" />
<ChevronDown size={16} className="text-gray-400 dark:text-gray-500 hidden md:block" />
</button>
</div>
</div>
Expand Down
28 changes: 14 additions & 14 deletions Frontend/src/components/ui/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ export function cn(...inputs) {
}

export const Card = ({ className, children, ...props }) => (
<div className={cn("bg-white rounded-2xl border border-gray-100 shadow-sm hover:shadow-md transition-all duration-200", className)} {...props}>
<div className={cn("bg-white dark:bg-gray-800 rounded-2xl border border-gray-100 dark:border-gray-700 shadow-sm hover:shadow-md transition-all duration-200", className)} {...props}>
{children}
</div>
);

export const Button = ({ className, variant = 'primary', size = 'md', ...props }) => {
const variants = {
primary: "bg-primary text-white shadow-lg shadow-indigo-200 hover:bg-primary-hover hover:shadow-indigo-300",
secondary: "bg-white text-gray-700 border border-gray-200 hover:bg-gray-50 hover:border-gray-300",
danger: "bg-red-50 text-red-600 hover:bg-red-100",
ghost: "text-gray-500 hover:text-gray-900 hover:bg-gray-50",
primary: "bg-primary text-white shadow-lg shadow-indigo-200 dark:shadow-indigo-900/50 hover:bg-primary-hover hover:shadow-indigo-300 dark:hover:shadow-indigo-900/70",
secondary: "bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600",
danger: "bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/50",
ghost: "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700",
};

const sizes = {
Expand All @@ -41,26 +41,26 @@ export const Button = ({ className, variant = 'primary', size = 'md', ...props }

export const Input = ({ className, label, error, ...props }) => (
<div className="w-full">
{label && <label className="block text-sm font-medium text-gray-700 mb-1.5">{label}</label>}
{label && <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">{label}</label>}
<input
className={cn(
"w-full px-4 py-3 rounded-xl border border-gray-200 bg-gray-50 focus:bg-white focus:border-primary focus:ring-4 focus:ring-primary-light/20 outline-none transition-all duration-200",
error && "border-red-500 focus:border-red-500 focus:ring-red-100",
"w-full px-4 py-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:bg-white dark:focus:bg-gray-700 focus:border-primary focus:ring-4 focus:ring-primary-light/20 dark:focus:ring-primary/30 outline-none transition-all duration-200",
error && "border-red-500 dark:border-red-400 focus:border-red-500 dark:focus:border-red-400 focus:ring-red-100 dark:focus:ring-red-900/30",
className
)}
{...props}
/>
{error && <p className="mt-1 text-xs text-red-500">{error}</p>}
{error && <p className="mt-1 text-xs text-red-500 dark:text-red-400">{error}</p>}
</div>
);

export const Badge = ({ children, variant = 'default', className }) => {
const variants = {
default: "bg-gray-100 text-gray-700",
success: "bg-green-100 text-green-700",
warning: "bg-yellow-100 text-yellow-700",
danger: "bg-red-100 text-red-700",
primary: "bg-indigo-100 text-indigo-700",
default: "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300",
success: "bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400",
warning: "bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400",
danger: "bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400",
primary: "bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-400",
};

return (
Expand Down
14 changes: 7 additions & 7 deletions Frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

@layer base {
body {
@apply bg-gray-50 text-gray-900 font-sans antialiased;
@apply bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 font-sans antialiased;
}
}

Expand All @@ -20,23 +20,23 @@
}

.btn-secondary {
@apply bg-white text-gray-700 border border-gray-200 hover:bg-gray-50 hover:border-gray-300;
@apply bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600;
}

.btn-danger {
@apply bg-red-50 text-red-600 hover:bg-red-100;
@apply bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/50;
}

.card {
@apply bg-white rounded-2xl border border-gray-100 shadow-sm hover:shadow-md transition-shadow duration-200;
@apply bg-white dark:bg-gray-800 rounded-2xl border border-gray-100 dark:border-gray-700 shadow-sm hover:shadow-md transition-shadow duration-200;
}

.input {
@apply w-full px-4 py-3 rounded-xl border border-gray-200 bg-gray-50 focus:bg-white focus:border-primary focus:ring-4 focus:ring-primary-light/20 outline-none transition-all duration-200;
@apply w-full px-4 py-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:bg-white dark:focus:bg-gray-700 focus:border-primary focus:ring-4 focus:ring-primary-light/20 dark:focus:ring-primary/30 outline-none transition-all duration-200;
}

.label {
@apply block text-sm font-medium text-gray-700 mb-1.5;
@apply block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5;
}
}

Expand All @@ -49,5 +49,5 @@
background: transparent;
}
::-webkit-scrollbar-thumb {
@apply bg-gray-300 rounded-full hover:bg-gray-400;
@apply bg-gray-300 dark:bg-gray-600 rounded-full hover:bg-gray-400 dark:hover:bg-gray-500;
}
Loading