d89304845a
- Backend: FastAPI, JWT auth (HS256), bcrypt, RBAC middleware - User CRUD (admin-only), soft-delete, pagination - Pydantic BaseSettings config, async SQLAlchemy 2.0 - 50 backend tests, 88% coverage - Frontend: Next.js 14 App Router, Tailwind, design tokens - Login page, auth context, API client with auto-refresh - i18n: next-intl, DE/EN (31 keys each) - 6 base UI components (Button, Input, Card, Table, Modal, Toast) - 12 frontend tests, npm build success
81 lines
1.9 KiB
TypeScript
81 lines
1.9 KiB
TypeScript
'use client';
|
|
|
|
import { useRouter } from 'next/navigation';
|
|
import { useEffect, useState, useCallback } from 'react';
|
|
import {
|
|
login as apiLogin,
|
|
getCurrentUser,
|
|
setTokens,
|
|
clearTokens,
|
|
isAuthenticated,
|
|
type UserResponse,
|
|
type ApiError,
|
|
} from './api';
|
|
|
|
export interface AuthState {
|
|
user: UserResponse | null;
|
|
loading: boolean;
|
|
error: string | null;
|
|
}
|
|
|
|
export function useAuth() {
|
|
const [state, setState] = useState<AuthState>({
|
|
user: null,
|
|
loading: true,
|
|
error: null,
|
|
});
|
|
|
|
const fetchUser = useCallback(async () => {
|
|
if (!isAuthenticated()) {
|
|
setState({ user: null, loading: false, error: null });
|
|
return;
|
|
}
|
|
try {
|
|
const user = await getCurrentUser();
|
|
setState({ user, loading: false, error: null });
|
|
} catch {
|
|
clearTokens();
|
|
setState({ user: null, loading: false, error: 'Session expired' });
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchUser();
|
|
}, [fetchUser]);
|
|
|
|
const login = useCallback(async (email: string, password: string) => {
|
|
setState({ user: null, loading: true, error: null });
|
|
try {
|
|
const tokens = await apiLogin(email, password);
|
|
setTokens(tokens);
|
|
const user = await getCurrentUser();
|
|
setState({ user, loading: false, error: null });
|
|
return user;
|
|
} catch (err) {
|
|
const message = (err as ApiError)?.error?.message || 'Login failed';
|
|
setState({ user: null, loading: false, error: message });
|
|
throw err;
|
|
}
|
|
}, []);
|
|
|
|
const logout = useCallback(() => {
|
|
clearTokens();
|
|
setState({ user: null, loading: false, error: null });
|
|
}, []);
|
|
|
|
return { ...state, login, logout, refresh: fetchUser };
|
|
}
|
|
|
|
export function useRequireAuth() {
|
|
const auth = useAuth();
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
if (!auth.loading && !auth.user) {
|
|
router.push('/login');
|
|
}
|
|
}, [auth.loading, auth.user, router]);
|
|
|
|
return auth;
|
|
}
|