diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index fc9e880d..30b1b291 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -2,6 +2,7 @@ import { ReactNode, useState } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { ThemeToggle } from './ThemeToggle'
import { Button } from './ui/Button'
+import { useAuth } from '../context/AuthContext'
interface LayoutProps {
children: ReactNode
@@ -10,6 +11,7 @@ interface LayoutProps {
export default function Layout({ children }: LayoutProps) {
const location = useLocation()
const [sidebarOpen, setSidebarOpen] = useState(false)
+ const { logout } = useAuth()
const navigation = [
{ name: 'Dashboard', path: '/', icon: '📊' },
@@ -63,6 +65,17 @@ export default function Layout({ children }: LayoutProps) {
)
})}
+
+
diff --git a/frontend/src/components/__tests__/Layout.test.tsx b/frontend/src/components/__tests__/Layout.test.tsx
index 88020954..4cc2c388 100644
--- a/frontend/src/components/__tests__/Layout.test.tsx
+++ b/frontend/src/components/__tests__/Layout.test.tsx
@@ -1,10 +1,17 @@
import { ReactNode } from 'react'
-import { describe, it, expect } from 'vitest'
+import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import Layout from '../Layout'
import { ThemeProvider } from '../../context/ThemeContext'
+// Mock AuthContext
+vi.mock('../../context/AuthContext', () => ({
+ useAuth: () => ({
+ logout: vi.fn(),
+ }),
+}))
+
const renderWithProviders = (children: ReactNode) => {
return render(
diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx
index 0f30588a..e30ad4e4 100644
--- a/frontend/src/context/AuthContext.tsx
+++ b/frontend/src/context/AuthContext.tsx
@@ -1,6 +1,5 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import client from '../api/client';
-import { AxiosResponse } from 'axios';
interface User {
user_id: number;
@@ -9,7 +8,7 @@ interface User {
interface AuthContextType {
user: User | null;
- login: () => void;
+ login: () => Promise;
logout: () => void;
isAuthenticated: boolean;
isLoading: boolean;
@@ -36,14 +35,16 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
checkAuth();
}, []);
- const login = () => {
+ const login = async () => {
// Token is stored in cookie by backend, but we might want to store it in memory or trigger a re-fetch
// Actually, if backend sets cookie, we just need to fetch /auth/me
- client.get('/auth/me').then((response: AxiosResponse) => {
- setUser(response.data);
- }).catch(() => {
- setUser(null);
- });
+ try {
+ const response = await client.get('/auth/me');
+ setUser(response.data);
+ } catch (error) {
+ setUser(null);
+ throw error;
+ }
};
const logout = async () => {
@@ -55,6 +56,40 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
setUser(null);
};
+ // Auto-logout logic
+ useEffect(() => {
+ if (!user) return;
+
+ const TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes
+ let timeoutId: ReturnType;
+
+ const resetTimer = () => {
+ if (timeoutId) clearTimeout(timeoutId);
+ timeoutId = setTimeout(() => {
+ console.log('Auto-logging out due to inactivity');
+ logout();
+ }, TIMEOUT_MS);
+ };
+
+ // Initial timer start
+ resetTimer();
+
+ // Event listeners for activity
+ const events = ['mousedown', 'keydown', 'scroll', 'touchstart'];
+ const handleActivity = () => resetTimer();
+
+ events.forEach(event => {
+ window.addEventListener(event, handleActivity);
+ });
+
+ return () => {
+ if (timeoutId) clearTimeout(timeoutId);
+ events.forEach(event => {
+ window.removeEventListener(event, handleActivity);
+ });
+ };
+ }, [user]);
+
return (
{children}
diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx
index af2bafc6..4bb99c2b 100644
--- a/frontend/src/pages/Login.tsx
+++ b/frontend/src/pages/Login.tsx
@@ -21,7 +21,7 @@ export default function Login() {
try {
await client.post('/auth/login', { email, password })
- login()
+ await login()
toast.success('Logged in successfully')
navigate('/')
} catch (err: any) {