diff --git a/src/App.tsx b/src/App.tsx
index 94e72c0..4f92cae 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,6 +1,7 @@
import { BrowserRouter, Routes, Route, Navigate, Outlet } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthProvider, useAuth } from './contexts/AuthContext';
+import { ThemeProvider } from './contexts/ThemeContext';
import { Layout } from './components/Layout';
import { Dashboard } from './pages/Dashboard';
import { Products } from './pages/Products';
@@ -59,39 +60,41 @@ function PublicRoute() {
function App() {
return (
-
-
-
- {/* Public routes */}
- }>
- } />
- } />
-
-
- {/* Protected routes */}
- }>
- }>
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+
+
+
+
+ {/* Public routes */}
+ }>
+ } />
+ } />
-
- {/* Catch all */}
- } />
-
-
-
+ {/* Protected routes */}
+ }>
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ {/* Catch all */}
+ } />
+
+
+
+
);
}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index 0ef8465..d982806 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -16,10 +16,13 @@ import {
Truck,
Coins,
QrCode,
+ Sun,
+ Moon,
} from 'lucide-react';
import { useState } from 'react';
import clsx from 'clsx';
import { useAuth } from '../contexts/AuthContext';
+import { useTheme } from '../contexts/ThemeContext';
const navigation = [
{ name: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
@@ -39,6 +42,7 @@ const navigation = [
export function Layout() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const { user, tenant, logout } = useAuth();
+ const { resolvedTheme, toggleTheme } = useTheme();
const navigate = useNavigate();
const handleLogout = () => {
@@ -56,7 +60,7 @@ export function Layout() {
};
return (
-
+
{/* Mobile sidebar */}
setSidebarOpen(false)}
/>
-
-
+
+
- MiChangarrito
+ MiChangarrito
-
@@ -88,8 +92,8 @@ export function Layout() {
clsx(
'flex items-center gap-3 px-3 py-2 rounded-lg transition-colors',
isActive
- ? 'bg-primary-50 text-primary-600'
- : 'text-gray-700 hover:bg-gray-100'
+ ? 'bg-primary-50 text-primary-600 dark:bg-primary-900/30 dark:text-primary-400'
+ : 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700'
)
}
>
@@ -103,12 +107,23 @@ export function Layout() {
{/* Desktop sidebar */}
-
-
+
+
- MiChangarrito
+ MiChangarrito
+
+ {resolvedTheme === 'dark' ? (
+
+ ) : (
+
+ )}
+
-
+
-
-
+
+
{tenant ? getInitials(tenant.name) : 'MC'}
-
{tenant?.name || 'Mi Negocio'}
-
{user?.name}
+
{tenant?.name || 'Mi Negocio'}
+
{user?.name}
@@ -157,14 +172,27 @@ export function Layout() {
{/* Main content */}
{/* Mobile header */}
-
-
setSidebarOpen(true)}>
-
-
-
-
-
MiChangarrito
+
+
+
setSidebarOpen(true)} className="dark:text-gray-300">
+
+
+
+
+ MiChangarrito
+
+
+ {resolvedTheme === 'dark' ? (
+
+ ) : (
+
+ )}
+
{/* Page content */}
diff --git a/src/contexts/ThemeContext.tsx b/src/contexts/ThemeContext.tsx
new file mode 100644
index 0000000..a6b0815
--- /dev/null
+++ b/src/contexts/ThemeContext.tsx
@@ -0,0 +1,103 @@
+import { createContext, useContext, useState, useEffect } from 'react';
+import type { ReactNode } from 'react';
+
+type Theme = 'light' | 'dark' | 'system';
+
+interface ThemeContextType {
+ theme: Theme;
+ resolvedTheme: 'light' | 'dark';
+ setTheme: (theme: Theme) => void;
+ toggleTheme: () => void;
+}
+
+const ThemeContext = createContext
(undefined);
+
+const STORAGE_KEY = 'michangarrito-theme';
+
+function getSystemTheme(): 'light' | 'dark' {
+ if (typeof window !== 'undefined' && window.matchMedia) {
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+ }
+ return 'light';
+}
+
+function getStoredTheme(): Theme {
+ if (typeof window !== 'undefined') {
+ const stored = localStorage.getItem(STORAGE_KEY);
+ if (stored === 'light' || stored === 'dark' || stored === 'system') {
+ return stored;
+ }
+ }
+ return 'system';
+}
+
+export function ThemeProvider({ children }: { children: ReactNode }) {
+ const [theme, setThemeState] = useState(getStoredTheme);
+ const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');
+
+ // Resolve the actual theme (handles 'system' preference)
+ useEffect(() => {
+ const resolved = theme === 'system' ? getSystemTheme() : theme;
+ setResolvedTheme(resolved);
+
+ // Apply class to document
+ const root = document.documentElement;
+ if (resolved === 'dark') {
+ root.classList.add('dark');
+ } else {
+ root.classList.remove('dark');
+ }
+ }, [theme]);
+
+ // Listen for system theme changes
+ useEffect(() => {
+ if (theme !== 'system') return;
+
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+ const handleChange = (e: MediaQueryListEvent) => {
+ const newTheme = e.matches ? 'dark' : 'light';
+ setResolvedTheme(newTheme);
+
+ const root = document.documentElement;
+ if (newTheme === 'dark') {
+ root.classList.add('dark');
+ } else {
+ root.classList.remove('dark');
+ }
+ };
+
+ mediaQuery.addEventListener('change', handleChange);
+ return () => mediaQuery.removeEventListener('change', handleChange);
+ }, [theme]);
+
+ const setTheme = (newTheme: Theme) => {
+ setThemeState(newTheme);
+ localStorage.setItem(STORAGE_KEY, newTheme);
+ };
+
+ const toggleTheme = () => {
+ const newTheme = resolvedTheme === 'light' ? 'dark' : 'light';
+ setTheme(newTheme);
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTheme() {
+ const context = useContext(ThemeContext);
+ if (context === undefined) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+}
diff --git a/src/index.css b/src/index.css
index c48d4d6..107701b 100644
--- a/src/index.css
+++ b/src/index.css
@@ -26,7 +26,12 @@
@layer base {
body {
- @apply bg-gray-50 text-gray-900;
+ @apply bg-gray-50 text-gray-900 transition-colors duration-200;
+ }
+
+ .dark body,
+ html.dark body {
+ @apply bg-gray-900 text-gray-100;
}
}
@@ -41,13 +46,16 @@
.btn-outline {
@apply border border-gray-300 hover:border-gray-400 text-gray-700 font-medium py-2 px-4 rounded-lg transition-colors;
+ @apply dark:border-gray-600 dark:hover:border-gray-500 dark:text-gray-300;
}
.card {
@apply bg-white rounded-xl shadow-sm border border-gray-200 p-4;
+ @apply dark:bg-gray-800 dark:border-gray-700;
}
.input {
@apply w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500;
+ @apply dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100 dark:placeholder-gray-400;
}
}
diff --git a/tailwind.config.js b/tailwind.config.js
index cc11449..af0fc77 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,5 +1,6 @@
/** @type {import('tailwindcss').Config} */
export default {
+ darkMode: 'class',
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",