feat(frontend): Implement complete authentication system
Implemented comprehensive JWT-based authentication with token refresh mechanism, user state management, and protected routes. Changes: - Upgraded API client from fetch to Axios with automatic token refresh interceptors - Created API configuration with centralized endpoint definitions - Implemented Zustand auth store for user state management with persistence - Created React Query hooks for login, register, logout, and current user - Built login and registration pages with form validation (Zod + React Hook Form) - Implemented AuthGuard component for route protection - Enhanced Header with user dropdown menu and logout functionality - Updated Sidebar with user information display at bottom - Added Team navigation item to sidebar - Configured environment variables for API base URL Technical Details: - JWT token storage in localStorage with secure key names - Automatic token refresh on 401 responses - Request queueing during token refresh to prevent race conditions - TypeScript strict typing throughout - ESLint compliant code (fixed type safety issues) - Proper error handling with user-friendly messages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
110
lib/hooks/useAuth.ts
Normal file
110
lib/hooks/useAuth.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiClient, tokenManager } from '../api/client';
|
||||
import { API_ENDPOINTS } from '../api/config';
|
||||
import { useAuthStore } from '@/stores/authStore';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface LoginCredentials {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface RegisterTenantData {
|
||||
email: string;
|
||||
password: string;
|
||||
fullName: string;
|
||||
tenantName: string;
|
||||
}
|
||||
|
||||
export function useLogin() {
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
const router = useRouter();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (credentials: LoginCredentials) => {
|
||||
const { data } = await apiClient.post(API_ENDPOINTS.LOGIN, credentials);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
tokenManager.setAccessToken(data.accessToken);
|
||||
tokenManager.setRefreshToken(data.refreshToken);
|
||||
|
||||
setUser({
|
||||
id: data.user.id,
|
||||
email: data.user.email,
|
||||
fullName: data.user.fullName,
|
||||
tenantId: data.user.tenantId,
|
||||
tenantName: data.user.tenantName,
|
||||
role: data.user.role,
|
||||
isEmailVerified: data.user.isEmailVerified,
|
||||
});
|
||||
|
||||
router.push('/dashboard');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRegisterTenant() {
|
||||
const router = useRouter();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (data: RegisterTenantData) => {
|
||||
const response = await apiClient.post(
|
||||
API_ENDPOINTS.REGISTER_TENANT,
|
||||
data
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
router.push('/login?registered=true');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useLogout() {
|
||||
const clearUser = useAuthStore((state) => state.clearUser);
|
||||
const queryClient = useQueryClient();
|
||||
const router = useRouter();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
try {
|
||||
await apiClient.post(API_ENDPOINTS.LOGOUT);
|
||||
} catch {
|
||||
// Ignore logout errors
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
tokenManager.clearTokens();
|
||||
clearUser();
|
||||
queryClient.clear();
|
||||
router.push('/login');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCurrentUser() {
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
const clearUser = useAuthStore((state) => state.clearUser);
|
||||
const setLoading = useAuthStore((state) => state.setLoading);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['currentUser'],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get(API_ENDPOINTS.ME);
|
||||
setUser(data);
|
||||
setLoading(false);
|
||||
return data;
|
||||
},
|
||||
enabled: !!tokenManager.getAccessToken(),
|
||||
retry: false,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
refetchOnWindowFocus: false,
|
||||
throwOnError: () => {
|
||||
clearUser();
|
||||
tokenManager.clearTokens();
|
||||
setLoading(false);
|
||||
return false;
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user