feat(authentication): implement frontend authentication system

- Created auth and user types (T10)
- Implemented API client with token handling (T11)
- Built AuthContext with JWT decoding (T12)
- Added ProtectedRoute component (T13)
- Created LoginPage, RegisterPage, DashboardPage (T14-T16)
- Updated App.tsx with routing and auth provider (T17)
- Added Dockerfile, nginx.conf for frontend deployment (T18-T19)
- Updated docker-compose.yml to include frontend service (T20)
- Updated .gitignore to exclude frontend build artifacts (T21)
- Removed unused App.css (T22)

Refs #2
This commit is contained in:
2026-04-01 13:37:37 -03:00
parent bf2cfbd9fc
commit 7b9a7192c1
32 changed files with 4450 additions and 1 deletions

View File

@@ -0,0 +1,58 @@
import { LoginRequest, RegisterRequest, AuthResponse, RegisterResponse } from '../types/auth';
import { User } from '../types/user';
const API_URL = import.meta.env.VITE_API_URL || '';
class ApiClient {
private async request<T>(input: RequestInfo, init?: RequestInit): Promise<T> {
const token = localStorage.getItem('token');
const headers = new Headers(init?.headers);
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
const response = await fetch(`${API_URL}${input}`, {
...init,
headers,
});
// Handle 401 Unauthorized - clear token and redirect to login
if (response.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
throw new Error('Unauthorized');
}
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `Error ${response.status}`);
}
return response.json();
}
async login(data: LoginRequest): Promise<AuthResponse> {
return this.request<AuthResponse>('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
}
async register(data: RegisterRequest): Promise<RegisterResponse> {
return this.request<RegisterResponse>('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
}
async getUsers(): Promise<User[]> {
return this.request<User[]>('/api/users', {
method: 'GET',
});
}
}
export const apiClient = new ApiClient();