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:
25
src/App.tsx
25
src/App.tsx
@@ -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 { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
// Import Leaflet CSS
|
// Import Leaflet CSS
|
||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
@@ -24,6 +25,10 @@ import {
|
|||||||
WhyCleanPage
|
WhyCleanPage
|
||||||
} from './pages';
|
} from './pages';
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { apiClient } from './services/api';
|
||||||
|
import { reportService } from './services/reportService';
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
@@ -39,11 +44,26 @@ if (!clerkPubKey) {
|
|||||||
throw new Error('Missing Clerk Publishable Key');
|
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() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<ClerkProvider publishableKey={clerkPubKey}>
|
<ClerkProvider publishableKey={clerkPubKey}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<BrowserRouter>
|
<ApiInitializer>
|
||||||
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* Public routes */}
|
{/* Public routes */}
|
||||||
<Route path="/sign-in/*" element={<SignInPage />} />
|
<Route path="/sign-in/*" element={<SignInPage />} />
|
||||||
@@ -134,6 +154,7 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
</ApiInitializer>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</ClerkProvider>
|
</ClerkProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// API service configuration and base client
|
// 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';
|
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 {
|
export class ApiClient {
|
||||||
private baseUrl: string;
|
private baseUrl: string;
|
||||||
|
private getToken?: () => Promise<string | null>;
|
||||||
|
|
||||||
constructor(baseUrl: string = API_BASE_URL) {
|
constructor(baseUrl: string = API_BASE_URL) {
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTokenGetter(getToken: () => Promise<string | null>) {
|
||||||
|
this.getToken = getToken;
|
||||||
|
}
|
||||||
|
|
||||||
private async request<T>(
|
private async request<T>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
options: RequestInit = {}
|
options: RequestInit = {}
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const url = `${this.baseUrl}${endpoint}`;
|
const url = `${this.baseUrl}${endpoint}`;
|
||||||
|
|
||||||
|
// Get auth token if available
|
||||||
|
const token = this.getToken ? await this.getToken() : null;
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
...options.headers,
|
...options.headers,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ export interface UploadPhotoResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ReportService {
|
class ReportService {
|
||||||
|
private getToken?: () => Promise<string | null>;
|
||||||
|
|
||||||
|
setTokenGetter(getToken: () => Promise<string | null>) {
|
||||||
|
this.getToken = getToken;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload a photo to the server
|
* Upload a photo to the server
|
||||||
*/
|
*/
|
||||||
@@ -31,8 +37,14 @@ class ReportService {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('photo', file);
|
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`, {
|
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:3005/api'}/reports/upload`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
},
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user