aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-13 07:01:40 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-13 07:01:40 -0800
commite31b68197ec16d2805ec14c2bf532a03f4739e92 (patch)
treebcda027df5c020ec6d12073ef1b132a6a14c2a86 /frontend/src
parentbd2508211760edbc1bad1d515587d08fd2ec99c9 (diff)
downloadneko-e31b68197ec16d2805ec14c2bf532a03f4739e92.tar.gz
neko-e31b68197ec16d2805ec14c2bf532a03f4739e92.tar.bz2
neko-e31b68197ec16d2805ec14c2bf532a03f4739e92.zip
Implement Frontend Logout with tests
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/App.css16
-rw-r--r--frontend/src/App.test.tsx21
-rw-r--r--frontend/src/App.tsx7
3 files changed, 42 insertions, 2 deletions
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 57800a4..69edabd 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -57,4 +57,20 @@ body {
padding: 2rem;
overflow-y: auto;
background: #fff;
+}
+
+.logout-btn {
+ background: transparent;
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.2s;
+ font-size: 0.9rem;
+}
+
+.logout-btn:hover {
+ background: rgba(255, 255, 255, 0.1);
+ border-color: rgba(255, 255, 255, 0.5);
} \ No newline at end of file
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 37a7fab..5614d7d 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import '@testing-library/jest-dom';
-import { render, screen, waitFor } from '@testing-library/react';
+import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import App from './App';
import { describe, it, expect, vi, beforeEach } from 'vitest';
@@ -30,5 +30,24 @@ describe('App', () => {
await waitFor(() => {
expect(screen.getByText(/neko reader/i)).toBeInTheDocument();
});
+
+ // Test Logout
+ const logoutBtn = screen.getByText(/logout/i);
+ expect(logoutBtn).toBeInTheDocument();
+
+ // Mock window.location
+ Object.defineProperty(window, 'location', {
+ configurable: true,
+ value: { href: '' },
+ });
+
+ (global.fetch as any).mockResolvedValueOnce({ ok: true });
+
+ fireEvent.click(logoutBtn);
+
+ await waitFor(() => {
+ expect(global.fetch).toHaveBeenCalledWith('/api/logout', expect.objectContaining({ method: 'POST' }));
+ expect(window.location.href).toBe('/login/');
+ });
});
});
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index bc4e097..7c9d555 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -40,7 +40,12 @@ function Dashboard() {
<header className="dashboard-header">
<h1>Neko Reader</h1>
<nav>
- {/* Add logout later */}
+ <button onClick={() => {
+ fetch('/api/logout', { method: 'POST' })
+ .then(() => window.location.href = '/login/');
+ }} className="logout-btn">
+ Logout
+ </button>
</nav>
</header>
<div className="dashboard-content">