diff --git a/src/blog/providers/Article.tsx b/src/blog/providers/Article.tsx index 4ecc5ef..a66cf0f 100644 --- a/src/blog/providers/Article.tsx +++ b/src/blog/providers/Article.tsx @@ -1,28 +1,27 @@ import React, { createContext, useState, useContext, useEffect } from 'react'; -import axios from 'axios'; -import { ArticleModel } from "../types/models"; -import { ArticleContextModel } from "../types/contexts"; +import { api } from '../utils/api'; +import { ArticleModel } from '../types/models'; +import { ArticleContextModel } from '../types/contexts'; +import { useAuth } from './Author'; const ArticleContext = createContext(undefined); -const API_BASE = import.meta.env.VITE_API_BASE_URL; - export const ArticleProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [articles, setArticles] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const { token } = useAuth(); // ✅ access token if needed + /** 🔹 Fetch articles (JWT automatically attached by api.ts interceptor) */ const fetchArticles = async () => { try { setLoading(true); setError(null); - // ✅ Use correct full endpoint from OpenAPI spec - const res = await axios.get(`${API_BASE}/articles`, { + const res = await api.get('/articles', { params: { skip: 0, limit: 10 }, }); - // ✅ Normalize if backend sends _id instead of id const formatted = res.data.map((a) => ({ ...a, id: a._id || undefined, @@ -31,15 +30,16 @@ export const ArticleProvider: React.FC<{ children: React.ReactNode }> = ({ child setArticles(formatted); } catch (err: any) { console.error('Failed to fetch articles:', err); - setError(err.message || 'Failed to fetch articles'); + setError(err.response?.data?.detail || 'Failed to fetch articles'); } finally { setLoading(false); } }; + /** 🔹 Auto-fetch articles whenever user logs in/out */ useEffect(() => { - fetchArticles(); - }, []); + if (token) fetchArticles(); + }, [token]); return ( diff --git a/src/blog/providers/Author.tsx b/src/blog/providers/Author.tsx index 4cd8ac6..ed48105 100644 --- a/src/blog/providers/Author.tsx +++ b/src/blog/providers/Author.tsx @@ -1,12 +1,10 @@ import React, { createContext, useState, useEffect, useContext } from 'react'; -import axios from 'axios'; -import { AuthorModel } from "../types/models"; -import { AuthContextModel } from "../types/contexts"; +import { api } from '../utils/api'; +import { AuthorModel } from '../types/models'; +import { AuthContextModel } from '../types/contexts'; const AuthContext = createContext(undefined); -const API_BASE = import.meta.env.VITE_API_BASE_URL; - export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [currentUser, setCurrentUser] = useState(null); const [authors, setAuthors] = useState([]); @@ -20,7 +18,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children setLoading(true); setError(null); - const res = await axios.post(`${API_BASE}/auth/login`, { email, password }); + const res = await api.post('/auth/login', { email, password }); const { access_token, user } = res.data; if (access_token) { @@ -44,17 +42,13 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children setAuthors([]); }; - /** 🔹 Fetch all authors (requires valid JWT) */ + /** 🔹 Fetch all authors (JWT handled by api interceptor) */ const refreshAuthors = async () => { - if (!token) return; try { setLoading(true); setError(null); - const res = await axios.get(`${API_BASE}/authors`, { - headers: { Authorization: `Bearer ${token}` }, - }); - + const res = await api.get('/authors'); setAuthors(res.data); } catch (err: any) { console.error('Failed to fetch authors:', err); @@ -68,9 +62,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const fetchCurrentUser = async () => { if (!token) return; try { - const res = await axios.get(`${API_BASE}/auth/me`, { - headers: { Authorization: `Bearer ${token}` }, - }); + const res = await api.get('/auth/me'); setCurrentUser(res.data); } catch (err: any) { console.error('Failed to fetch current user:', err); @@ -78,6 +70,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children } }; + /** 🔹 On mount, try to fetch user if token exists */ useEffect(() => { if (token) fetchCurrentUser(); }, [token]); diff --git a/src/blog/utils/api.ts b/src/blog/utils/api.ts new file mode 100644 index 0000000..671417f --- /dev/null +++ b/src/blog/utils/api.ts @@ -0,0 +1,33 @@ +// src/utils/api.ts +import axios from 'axios'; + +const API_BASE = import.meta.env.VITE_API_BASE_URL; + +export const api = axios.create({ + baseURL: API_BASE, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// 🔹 Attach token from localStorage before each request +api.interceptors.request.use((config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +// 🔹 Handle expired or invalid tokens globally +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + console.warn('Token expired or invalid. Logging out...'); + localStorage.removeItem('token'); + // Optionally: trigger a redirect or event + } + return Promise.reject(error); + } +); diff --git a/src/main.jsx b/src/main.jsx index 2ef2626..d93e38c 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,14 +2,17 @@ import * as React from 'react'; import { createRoot } from 'react-dom/client'; import Blog from './blog/Blog'; import { ArticleProvider } from './blog/providers/Article'; +import { AuthProvider } from './blog/providers/Author'; const rootElement = document.getElementById('root'); const root = createRoot(rootElement); root.render( - - - + + + + + , );