feat(dashboard): logout button + admin user-management window
Logout: new sidebar link 'Log out (username)' that POSTs /api/logout (clears session cookie) and navigates to /login. Visible to everyone. Replaces 'no logout functionality' state where users could only get out by deleting cookies manually. Admin window: new 'Admin · Users' window (only shown when current user.is_admin) lists all users in a table with: - Add user (username + password + admin checkbox) - Reset password inline per row - Toggle admin per row - Delete user per row (blocked for self) Wraps the existing /api-admin/users CRUD endpoints in main.py. Plumbing: useCurrentUser hook fetches /me on mount; apiPatch+apiDelete helpers added to api/client.ts; new endpoint wrappers exported from api/endpoints.ts; AdminUsersWindow.tsx registered in WindowRenderer under id prefix 'adminusers'; CSS for admin table/form/buttons and the muted-red logout link. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
88e9e88f46
commit
1c1c43d28b
23 changed files with 521 additions and 50 deletions
25
frontend/src/hooks/useCurrentUser.ts
Normal file
25
frontend/src/hooks/useCurrentUser.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { getCurrentUser, type CurrentUser } from '../api/endpoints';
|
||||
|
||||
/**
|
||||
* Returns the currently-logged-in dashboard user, or null if not logged in /
|
||||
* not yet loaded. Useful for conditionally showing admin-only UI bits.
|
||||
*
|
||||
* Fetches `/me` once on mount. Cheap — the endpoint just decodes the
|
||||
* session cookie and returns {username, is_admin}.
|
||||
*/
|
||||
export function useCurrentUser(): { user: CurrentUser | null; loading: boolean } {
|
||||
const [user, setUser] = useState<CurrentUser | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
getCurrentUser()
|
||||
.then(u => { if (!cancelled) setUser(u); })
|
||||
.catch(() => { if (!cancelled) setUser(null); })
|
||||
.finally(() => { if (!cancelled) setLoading(false); });
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
|
||||
return { user, loading };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue