Page admin : appelle l'API au lieu du front, et resynchronise le rôle
Build & Deploy / build (push) Successful in 20s
Build & Deploy / build (push) Successful in 20s
Admin.tsx faisait ses 8 appels en relatif (`/admin/users?token=…`), donc vers le nginx du front et non vers l'API. Le fallback SPA répondait index.html en HTTP 200 : le res.json() partait en erreur de parsing — « The string did not match the expected pattern » sous Safari. Les appels passent désormais par API_URL, comme le reste du front. App.tsx ne lisait le user que depuis localStorage, instantané pris au login : une promotion en admin n'y apparaissait jamais et AdminRoute éjectait le compte de /admin jusqu'à reconnexion. Le profil est resynchronisé depuis /me au montage. Restent 404 côté API : /admin/scrapers/logs et /admin/scrapers/run, que la page appelle mais que le backend n'expose pas (les scrapers tournent via le cron hôte). Le panneau affichera une erreur franche au lieu d'une erreur de parsing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-1
@@ -21,7 +21,7 @@ import BackToTop from "./components/BackToTop";
|
||||
|
||||
import styles from "./App.module.css";
|
||||
|
||||
import { getDocuments, type ApiDocument } from "./services/theapi";
|
||||
import { getDocuments, API_URL, type ApiDocument } from "./services/theapi";
|
||||
|
||||
type NavItem =
|
||||
| "home"
|
||||
@@ -104,6 +104,22 @@ export default function App() {
|
||||
: location.pathname === "/admin"? "admin"
|
||||
: "home";
|
||||
|
||||
// Le user de localStorage est un instantané pris au login : un changement de
|
||||
// rôle en base n'y apparaît jamais. On le resynchronise depuis /me au montage,
|
||||
// sinon un compte promu admin reste bloqué hors de /admin jusqu'à reconnexion.
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("authToken");
|
||||
if (!token) return;
|
||||
|
||||
fetch(`${API_URL}/me?token=${encodeURIComponent(token)}`)
|
||||
.then((res) => (res.ok ? res.json() : Promise.reject(res.status)))
|
||||
.then((freshUser: User) => {
|
||||
localStorage.setItem("user", JSON.stringify(freshUser));
|
||||
setAuthState((prev) => ({ ...prev, user: freshUser }));
|
||||
})
|
||||
.catch((err) => console.error("Rafraîchissement du profil impossible :", err));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (authState.isAuthenticated) {
|
||||
getDocuments()
|
||||
|
||||
+9
-8
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import styles from '../styles/Admin.module.css'
|
||||
import { API_URL } from '../services/theapi'
|
||||
import { FileText, Database, Users } from 'lucide-react'
|
||||
|
||||
type User = { id: string; email: string; full_name?: string; role: 'admin' | 'user' }
|
||||
@@ -35,23 +36,23 @@ export default function Admin() {
|
||||
setLoading(true)
|
||||
try{
|
||||
if(tab==='users'){
|
||||
const res = await fetch(`/admin/users?token=${token}`)
|
||||
const res = await fetch(`${API_URL}/admin/users?token=${token}`)
|
||||
if(!res.ok) throw new Error('Impossible de charger les utilisateurs')
|
||||
const data = await res.json()
|
||||
setUsers(data || [])
|
||||
} else if(tab==='documents'){
|
||||
const res = await fetch('/documents')
|
||||
const res = await fetch(`${API_URL}/documents`)
|
||||
if(!res.ok) throw new Error('Impossible de charger les documents')
|
||||
const data = await res.json()
|
||||
setDocs(data || [])
|
||||
} else if(tab==='stats'){
|
||||
const res = await fetch(`/admin/stats?token=${token}`)
|
||||
const res = await fetch(`${API_URL}/admin/stats?token=${token}`)
|
||||
if(!res.ok) throw new Error('Impossible de charger les statistiques')
|
||||
const data = await res.json()
|
||||
setStats(data)
|
||||
} else if(tab==='scrapers'){
|
||||
// logs endpoint (fallback)
|
||||
const res = await fetch(`/admin/scrapers/logs?token=${token}`)
|
||||
const res = await fetch(`${API_URL}/admin/scrapers/logs?token=${token}`)
|
||||
if(!res.ok) throw new Error('Impossible de charger les logs de scrapers')
|
||||
const data = await res.json()
|
||||
setLogs(data || [])
|
||||
@@ -74,7 +75,7 @@ export default function Admin() {
|
||||
async function deleteUser(id: string){
|
||||
try{
|
||||
setLoading(true)
|
||||
const res = await fetch(`/admin/users/${id}?token=${token}`, { method: 'DELETE' })
|
||||
const res = await fetch(`${API_URL}/admin/users/${id}?token=${token}`, { method: 'DELETE' })
|
||||
if(!res.ok) throw new Error('Suppression impossible')
|
||||
setUsers(u=>u.filter(x=>x.id!==id))
|
||||
window.dispatchEvent(new CustomEvent('app:toast', { detail: { message: 'Utilisateur supprimé', type: 'success' } }))
|
||||
@@ -89,7 +90,7 @@ export default function Admin() {
|
||||
try{
|
||||
setLoading(true)
|
||||
const newRole = u.role === 'admin' ? 'user' : 'admin'
|
||||
const res = await fetch(`/admin/users/${u.id}/role?token=${token}`, { method: 'PATCH', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ role: newRole }) })
|
||||
const res = await fetch(`${API_URL}/admin/users/${u.id}/role?token=${token}`, { method: 'PATCH', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ role: newRole }) })
|
||||
if(!res.ok) throw new Error('Impossible de modifier le rôle')
|
||||
setUsers(list=>list.map(x=> x.id===u.id ? {...x, role:newRole} : x))
|
||||
window.dispatchEvent(new CustomEvent('app:toast', { detail: { message: `Rôle mis à jour: ${newRole}`, type: 'success' } }))
|
||||
@@ -99,7 +100,7 @@ export default function Admin() {
|
||||
async function deleteDocument(id:string){
|
||||
try{
|
||||
setLoading(true)
|
||||
const res = await fetch(`/admin/documents/${id}?token=${token}`, { method: 'DELETE' })
|
||||
const res = await fetch(`${API_URL}/admin/documents/${id}?token=${token}`, { method: 'DELETE' })
|
||||
if(!res.ok) throw new Error('Suppression impossible')
|
||||
setDocs(d=>d.filter(x=>x.id!==id))
|
||||
window.dispatchEvent(new CustomEvent('app:toast', { detail: { message: 'Document supprimé', type: 'success' } }))
|
||||
@@ -109,7 +110,7 @@ export default function Admin() {
|
||||
async function runScraper(source: 'CIR'|'SCCS'){
|
||||
try{
|
||||
setLoading(true)
|
||||
const res = await fetch(`/admin/scrapers/run?source=${source}&token=${token}`, { method: 'POST' })
|
||||
const res = await fetch(`${API_URL}/admin/scrapers/run?source=${source}&token=${token}`, { method: 'POST' })
|
||||
if(!res.ok) throw new Error('Erreur lors du lancement')
|
||||
const entry = await res.json()
|
||||
setLogs(l=>[entry, ...l].slice(0,5))
|
||||
|
||||
Reference in New Issue
Block a user