Add Clerk authentication to API requests

- Update API client to include Bearer token in requests
- Add token getter method to reportService for photo uploads
- Create ApiInitializer component to set up auth tokens
- Use Clerk's getToken() to retrieve session tokens
- Fix 401 Unauthorized errors on report submission
This commit is contained in:
2025-11-16 17:34:07 +00:00
parent ae19d9b690
commit 48e75465ab
3 changed files with 45 additions and 2 deletions

View File

@@ -1,6 +1,7 @@
import { ClerkProvider } from '@clerk/clerk-react';
import { ClerkProvider, useAuth } from '@clerk/clerk-react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { useEffect } from 'react';
// Import Leaflet CSS
import 'leaflet/dist/leaflet.css';
@@ -24,6 +25,10 @@ import {
WhyCleanPage
} from './pages';
// Services
import { apiClient } from './services/api';
import { reportService } from './services/reportService';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
@@ -39,11 +44,26 @@ if (!clerkPubKey) {
throw new Error('Missing Clerk Publishable Key');
}
// Component to initialize API clients with auth token
function ApiInitializer({ children }: { children: React.ReactNode }) {
const { getToken } = useAuth();
useEffect(() => {
// Set up token getter for API clients
const tokenGetter = () => getToken();
apiClient.setTokenGetter(tokenGetter);
reportService.setTokenGetter(tokenGetter);
}, [getToken]);
return <>{children}</>;
}
function App() {
return (
<ClerkProvider publishableKey={clerkPubKey}>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<ApiInitializer>
<BrowserRouter>
<Routes>
{/* Public routes */}
<Route path="/sign-in/*" element={<SignInPage />} />
@@ -134,6 +154,7 @@ function App() {
/>
</Routes>
</BrowserRouter>
</ApiInitializer>
</QueryClientProvider>
</ClerkProvider>
);

View File

@@ -1,4 +1,5 @@
// API service configuration and base client
import { useAuth } from '@clerk/clerk-react';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api';
@@ -11,21 +12,30 @@ export interface ApiError {
export class ApiClient {
private baseUrl: string;
private getToken?: () => Promise<string | null>;
constructor(baseUrl: string = API_BASE_URL) {
this.baseUrl = baseUrl;
}
setTokenGetter(getToken: () => Promise<string | null>) {
this.getToken = getToken;
}
private async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
// Get auth token if available
const token = this.getToken ? await this.getToken() : null;
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
},
});

View File

@@ -24,6 +24,12 @@ export interface UploadPhotoResponse {
}
class ReportService {
private getToken?: () => Promise<string | null>;
setTokenGetter(getToken: () => Promise<string | null>) {
this.getToken = getToken;
}
/**
* Upload a photo to the server
*/
@@ -31,8 +37,14 @@ class ReportService {
const formData = new FormData();
formData.append('photo', file);
// Get auth token
const token = this.getToken ? await this.getToken() : null;
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:3005/api'}/reports/upload`, {
method: 'POST',
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: formData,
});