Skip to content
Merged
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
39 changes: 38 additions & 1 deletion frontend/src/App.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
:root {
/* Light theme variables */
--bg-primary: #f9fafb;
--bg-secondary: #ffffff;
--text-primary: #111827;
--text-secondary: #4b5563;
--accent-primary: #3b82f6;
--accent-secondary: #60a5fa;
--border-color: #e5e7eb;
--shadow-color: rgba(0, 0, 0, 0.1);

/* Animation durations */
--transition-duration: 300ms;
--transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
}

:root[class~="dark"] {
/* Dark theme variables */
--bg-primary: #111827;
--bg-secondary: #1f2937;
--text-primary: #f9fafb;
--text-secondary: #d1d5db;
--accent-primary: #60a5fa;
--accent-secondary: #93c5fd;
--border-color: #374151;
--shadow-color: rgba(0, 0, 0, 0.25);
}

/* Apply transitions to theme changes */
*, *::before, *::after {
transition: background-color var(--transition-duration) var(--transition-timing),
border-color var(--transition-duration) var(--transition-timing),
color var(--transition-duration) var(--transition-timing),
box-shadow var(--transition-duration) var(--transition-timing);
}

.App {
min-height: 100vh;
background-color: #f9fafb;
background-color: var(--bg-primary);
color: var(--text-primary);
}

/* Loading spinner */
Expand Down
88 changes: 53 additions & 35 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { Toaster } from 'react-hot-toast';
import Layout from './components/Layout/Layout';
import { getUserPreferences, setUserPreferences } from './services/userPreferences';
import SortingVisualizer from './pages/SortingVisualizer';
import GraphVisualizer from './pages/GraphVisualizer';
import StringVisualizer from './pages/StringVisualizer';
Expand All @@ -13,71 +14,88 @@ import ContributorsPage from './pages/ContributorsPage';
import './App.css';

function App() {
// Dark mode state with persistence
const [darkMode, setDarkMode] = useState(() => {
// Theme state with persistence
const [theme, setTheme] = useState(() => {
// Check system preference first
const systemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
try {
const saved = localStorage.getItem('darkMode');
return saved ? JSON.parse(saved) : false;
const { theme: savedTheme } = getUserPreferences();
return savedTheme || systemPreference;
} catch (error) {
console.error('Error loading dark mode preference:', error);
return false;
console.error('Error loading theme preference:', error);
return systemPreference;
}
});

// Save dark mode preference
useEffect(() => {
try {
localStorage.setItem('darkMode', JSON.stringify(darkMode));
// Update document class for global styling
if (darkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
} catch (error) {
console.error('Error saving dark mode preference:', error);
// Update theme and save preference
const handleThemeChange = (newTheme) => {
setTheme(newTheme);

// Save to localStorage
const preferences = getUserPreferences();
setUserPreferences({ ...preferences, theme: newTheme });

// Update document class for global styling
if (newTheme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [darkMode]);
};

// Listen for system theme changes
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = (e) => {
const systemTheme = e.matches ? 'dark' : 'light';
handleThemeChange(systemTheme);
};

mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, []);

// Apply theme on mount and changes
useEffect(() => {
handleThemeChange(theme);
}, [theme]);

return (
<div className={`min-h-screen transition-colors duration-300 ${
darkMode ? 'dark bg-gray-900' : 'bg-white'
}`}>
<div className="App">
<Router>
<Layout darkMode={darkMode} setDarkMode={setDarkMode}>
<Layout theme={theme} onThemeChange={handleThemeChange}>
<Routes>
<Route
path="/"
element={<HomePage darkMode={darkMode} setDarkMode={setDarkMode} />}
element={<HomePage theme={theme} />}
/>
<Route
path="/sorting"
element={<SortingVisualizer darkMode={darkMode} setDarkMode={setDarkMode} />}
element={<SortingVisualizer theme={theme} />}
/>
<Route
path="/graph"
element={<GraphVisualizer darkMode={darkMode} setDarkMode={setDarkMode} />}
element={<GraphVisualizer theme={theme} />}
/>
<Route
path="/string"
element={<StringVisualizer darkMode={darkMode} setDarkMode={setDarkMode} />}
element={<StringVisualizer theme={theme} />}
/>
<Route
path="/dp"
element={<DPVisualizer darkMode={darkMode} setDarkMode={setDarkMode} />}
element={<DPVisualizer theme={theme} />}
/>
<Route
path="/about"
element={<AboutPage darkMode={darkMode} setDarkMode={setDarkMode} />}
element={<AboutPage theme={theme} />}
/>
<Route
path="/docs"
element={<DocumentationPage darkMode={darkMode} setDarkMode={setDarkMode} />}
element={<DocumentationPage theme={theme} />}
/>
<Route
path="/contributors"
element={<ContributorsPage darkMode={darkMode} setDarkMode={setDarkMode} />}
element={<ContributorsPage theme={theme} />}
/>
</Routes>
</Layout>
Expand All @@ -89,9 +107,9 @@ function App() {
toastOptions={{
duration: 3000,
style: {
background: darkMode ? '#374151' : '#ffffff',
color: darkMode ? '#ffffff' : '#000000',
border: `1px solid ${darkMode ? '#4B5563' : '#E5E7EB'}`,
background: theme === 'dark' ? '#374151' : '#ffffff',
color: theme === 'dark' ? '#ffffff' : '#000000',
border: `1px solid ${theme === 'dark' ? '#4B5563' : '#E5E7EB'}`,
},
}}
/>
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/components/Layout/Footer.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FaGithub, FaLinkedin, FaTwitter } from "react-icons/fa";

const Footer = ({ darkMode }) => {
const Footer = ({ theme }) => {
const isDark = theme === 'dark';
const currentYear = new Date().getFullYear();

const socialLinks = [
Expand Down Expand Up @@ -35,7 +36,7 @@ const Footer = ({ darkMode }) => {
return (
<footer
className={`py-12 transition-all duration-500 ${
darkMode ? "bg-gray-900 text-gray-200" : "bg-gray-50 text-gray-800"
isDark ? "bg-gray-900 text-gray-200" : "bg-gray-50 text-gray-800"
}`}
>
{/* Top Row: Social & Quick Links */}
Expand Down Expand Up @@ -70,7 +71,7 @@ const Footer = ({ darkMode }) => {
<div className="flex-1">
<h3
className={`text-lg font-semibold mb-4 ${
darkMode ? "text-white" : "text-gray-900"
isDark ? "text-white" : "text-gray-900"
}`}
>
Quick Links
Expand All @@ -92,7 +93,7 @@ const Footer = ({ darkMode }) => {
{/* Divider */}
<div
className={`mt-10 border-t ${
darkMode ? "border-gray-700" : "border-gray-300"
isDark ? "border-gray-700" : "border-gray-300"
}`}
></div>

Expand Down
28 changes: 4 additions & 24 deletions frontend/src/components/Layout/Layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,12 @@ import React, { useState, useEffect } from "react";
import Navbar from "./Navbar";
import Footer from "./Footer";

const Layout = ({ children }) => {
const [darkMode, setDarkMode] = useState(() => {
// Check localStorage for saved theme preference
const saved = localStorage.getItem("darkMode");
return saved ? JSON.parse(saved) : false;
});

// Save theme preference to localStorage
useEffect(() => {
localStorage.setItem("darkMode", JSON.stringify(darkMode));
}, [darkMode]);

// Apply theme to document
useEffect(() => {
if (darkMode) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}, [darkMode]);

const Layout = ({ children, theme, onThemeChange }) => {
return (
<div className={`min-h-screen ${darkMode ? "dark" : ""}`}>
<Navbar darkMode={darkMode} setDarkMode={setDarkMode} />
<div className="min-h-screen">
<Navbar theme={theme} onThemeChange={onThemeChange} />
<main className="pt-16">{children}</main>
<Footer darkMode={darkMode} />
<Footer theme={theme} />
</div>
);
};
Expand Down
38 changes: 14 additions & 24 deletions frontend/src/components/Layout/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { useState, useEffect, useRef } from 'react';
import { Menu, X, ChevronDown, User, Settings, LogOut, Moon, Sun, Zap, BarChart3, Network, Code, BookOpen, Type, Layers, Info } from 'lucide-react';
import { Menu, X, ChevronDown, User, Settings, LogOut, Zap, BarChart3, Network, Code, BookOpen, Type, Layers, Info } from 'lucide-react';
import { Link, useLocation } from 'react-router-dom';
import ThemeToggle from './ThemeToggle';

const Navbar = ({ darkMode, setDarkMode }) => {
const Navbar = ({ theme, onThemeChange }) => {
const isDark = theme === 'dark';
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isProductsOpen, setIsProductsOpen] = useState(false);
const [isProfileOpen, setIsProfileOpen] = useState(false);
Expand Down Expand Up @@ -83,12 +85,12 @@ const Navbar = ({ darkMode, setDarkMode }) => {
fixed top-0 left-0 right-0 z-50 transition-all duration-200
${isScrolled
? `backdrop-blur-xl border-b ${
darkMode
isDark
? 'bg-gray-900/80 border-gray-800'
: 'bg-white/80 border-gray-200'
}`
: `backdrop-blur-sm ${
darkMode
isDark
? 'bg-gray-900/40 border-gray-800/40'
: 'bg-white/40 border-gray-200/40'
} border-b`
Expand All @@ -106,7 +108,7 @@ const Navbar = ({ darkMode, setDarkMode }) => {
transition-colors duration-200 focus-visible:outline-none
focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2
rounded-lg px-2 py-1
${darkMode
${isDark
? 'text-white hover:text-blue-400 focus-visible:ring-offset-gray-900'
: 'text-gray-900 hover:text-blue-600 focus-visible:ring-offset-white'
}
Expand Down Expand Up @@ -134,8 +136,8 @@ const Navbar = ({ darkMode, setDarkMode }) => {
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-blue-500 focus-visible:ring-offset-2
${isActive(path)
? (darkMode ? 'bg-blue-600 text-white' : 'bg-blue-600 text-white')
: (darkMode
? 'bg-blue-600 text-white'
: (isDark
? 'text-gray-300 hover:text-white hover:bg-gray-700'
: 'text-gray-700 hover:text-gray-900 hover:bg-gray-100'
)
Expand All @@ -152,19 +154,7 @@ const Navbar = ({ darkMode, setDarkMode }) => {
{/* Right side items */}
<div className="hidden md:flex items-center space-x-3">
{/* Theme Toggle */}
<button
onClick={() => setDarkMode(!darkMode)}
className={`
p-2 rounded-lg transition-all
${darkMode
? 'bg-yellow-500/20 text-yellow-300 hover:bg-yellow-500/30'
: 'bg-gray-800/20 text-gray-600 hover:bg-gray-800/30'
}
`}
aria-label="Toggle theme"
>
{darkMode ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
</button>
<ThemeToggle theme={theme} onToggle={onThemeChange} />
</div>

{/* Mobile menu button */}
Expand All @@ -175,7 +165,7 @@ const Navbar = ({ darkMode, setDarkMode }) => {
p-2 rounded-lg transition-colors duration-200
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-blue-500 focus-visible:ring-offset-2
${darkMode
${isDark
? 'hover:bg-gray-800 text-gray-300 hover:text-white focus-visible:ring-offset-gray-900'
: 'hover:bg-gray-100 text-gray-600 hover:text-gray-900 focus-visible:ring-offset-white'
}
Expand All @@ -191,7 +181,7 @@ const Navbar = ({ darkMode, setDarkMode }) => {
{isMenuOpen && (
<div className={`
md:hidden border-t backdrop-blur-xl
${darkMode ? 'bg-gray-900/95 border-gray-800' : 'bg-white/95 border-gray-200'}
${isDark ? 'bg-gray-900/95 border-gray-800' : 'bg-white/95 border-gray-200'}
`}>
<div className="px-4 py-3 space-y-1">
{navItems.map(({ path, label, icon: Icon }) => (
Expand All @@ -203,7 +193,7 @@ const Navbar = ({ darkMode, setDarkMode }) => {
transition-colors duration-200
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-blue-500 focus-visible:ring-offset-2
${darkMode
${isDark
? `hover:bg-gray-800 focus-visible:ring-offset-gray-900
${isActive(path) ? 'bg-gray-800 text-white' : 'text-gray-300'}`
: `hover:bg-gray-100 focus-visible:ring-offset-white
Expand All @@ -227,7 +217,7 @@ const Navbar = ({ darkMode, setDarkMode }) => {
transition-all duration-200
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-blue-500 focus-visible:ring-offset-2
${darkMode ? 'focus-visible:ring-offset-gray-900' : 'focus-visible:ring-offset-white'}
${isDark ? 'focus-visible:ring-offset-gray-900' : 'focus-visible:ring-offset-white'}
`}
>
Get Started
Expand Down
Loading
Loading