196 lines
6.0 KiB
TypeScript
196 lines
6.0 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Routes, Route, Navigate, useLocation, useNavigate } from "react-router-dom";
|
|
|
|
import AuthPage from "./pages/AuthPage";
|
|
import HomePage from "./pages/Home";
|
|
import DocumentsPage from "./pages/Documents";
|
|
import IngredientSearchPage from "./pages/IngredientSearchPage";
|
|
import RecentUpdatesPage from "./pages/RecentUpdatesPage";
|
|
import AboutPage from "./pages/AboutPage";
|
|
import ContactPage from "./pages/ContactPage";
|
|
import CGU from "./pages/CGU";
|
|
import MentionsLegales from "./pages/MentionsLegales";
|
|
import Admin from "./pages/Admin";
|
|
import AdminRoute from "./components/AdminRoute";
|
|
|
|
import Sidebar from "./components/Sidebar";
|
|
import Footer from "./components/Footer";
|
|
import CookieBanner from "./components/CookieBanner";
|
|
import Toasts from "./components/Toast";
|
|
import BackToTop from "./components/BackToTop";
|
|
|
|
import styles from "./App.module.css";
|
|
|
|
import { getDocuments, API_URL, type ApiDocument } from "./services/theapi";
|
|
|
|
type NavItem =
|
|
| "home"
|
|
| "ingredient-search"
|
|
| "documents"
|
|
| "recent-updates"
|
|
| "about"
|
|
| "contact"
|
|
| "cgu"
|
|
| "mentions-legales"
|
|
| "admin";
|
|
|
|
interface User {
|
|
id: number;
|
|
email: string;
|
|
full_name: string | null;
|
|
}
|
|
|
|
interface AuthState {
|
|
user: User | null;
|
|
isAuthenticated: boolean;
|
|
loading: boolean;
|
|
}
|
|
|
|
export default function App() {
|
|
const [recentDocuments, setRecentDocuments] = useState<ApiDocument[]>([]);
|
|
const [authState, setAuthState] = useState<AuthState>(() => {
|
|
const savedToken = localStorage.getItem("authToken");
|
|
const savedUser = localStorage.getItem("user");
|
|
|
|
if (savedToken && savedUser) {
|
|
const parsedUser = JSON.parse(savedUser);
|
|
return {
|
|
user: parsedUser,
|
|
isAuthenticated: true,
|
|
loading: false,
|
|
};
|
|
}
|
|
|
|
return {
|
|
user: null,
|
|
isAuthenticated: false,
|
|
loading: false,
|
|
};
|
|
});
|
|
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
|
|
const handleAuthSuccess = (newToken: string, newUser: User) => {
|
|
localStorage.setItem("authToken", newToken);
|
|
localStorage.setItem("user", JSON.stringify(newUser));
|
|
setAuthState({
|
|
user: newUser,
|
|
isAuthenticated: true,
|
|
loading: false,
|
|
});
|
|
navigate("/");
|
|
};
|
|
|
|
const handleLogout = () => {
|
|
localStorage.removeItem("authToken");
|
|
localStorage.removeItem("user");
|
|
setAuthState({
|
|
user: null,
|
|
isAuthenticated: false,
|
|
loading: false,
|
|
});
|
|
navigate("/");
|
|
};
|
|
|
|
const activeNav: NavItem =
|
|
location.pathname === "/ingredient-search" ? "ingredient-search"
|
|
: location.pathname === "/documents" ? "documents"
|
|
: location.pathname === "/recent-updates" ? "recent-updates"
|
|
: location.pathname === "/about" ? "about"
|
|
: location.pathname === "/contact" ? "contact"
|
|
: location.pathname === "/cgu" ? "cgu"
|
|
: location.pathname === "/mentions-legales"? "mentions-legales"
|
|
: location.pathname === "/admin"? "admin"
|
|
: "home";
|
|
|
|
|
|
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()
|
|
.then((data) => {
|
|
setRecentDocuments(Array.isArray(data) ? data.slice(0, 4) : []);
|
|
})
|
|
.catch((err) => {
|
|
console.error("API ERROR:", err);
|
|
setRecentDocuments([]);
|
|
});
|
|
}
|
|
}, [authState.isAuthenticated]);
|
|
|
|
const handleNavClick = (item: NavItem) => {
|
|
const path =
|
|
item === "home" ? "/"
|
|
: item === "documents" ? "/documents"
|
|
: item === "ingredient-search" ? "/ingredient-search"
|
|
: item === "recent-updates" ? "/recent-updates"
|
|
: item === "about" ? "/about"
|
|
: item === "contact" ? "/contact"
|
|
: item === "cgu" ? "/cgu"
|
|
: item === 'admin' ? '/admin'
|
|
: "/mentions-legales";
|
|
navigate(path);
|
|
};
|
|
|
|
if (authState.loading) {
|
|
return (
|
|
<div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "#f8fafc" }}>
|
|
<p style={{ color: "#475569", fontSize: "1.125rem" }}>Loading application...</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
|
|
if (!authState.isAuthenticated) {
|
|
return (
|
|
<>
|
|
<AuthPage onAuthSuccess={handleAuthSuccess} />
|
|
<CookieBanner />
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={styles.app}>
|
|
<Toasts />
|
|
<div className={styles.contentWrapper}>
|
|
<Sidebar
|
|
activeNav={activeNav}
|
|
onNavClick={handleNavClick}
|
|
onLogout={handleLogout}
|
|
user={authState.user}
|
|
/>
|
|
<main className={styles.main}>
|
|
<Routes>
|
|
<Route path="/" element={<HomePage handleNavClick={handleNavClick} recentDocuments={recentDocuments} />} />
|
|
<Route path="/documents" element={<DocumentsPage />} />
|
|
<Route path="/ingredient-search" element={<IngredientSearchPage />} />
|
|
<Route path="/recent-updates" element={<RecentUpdatesPage />} />
|
|
<Route path="/about" element={<AboutPage />} />
|
|
<Route path="/contact" element={<ContactPage />} />
|
|
<Route path="/cgu" element={<CGU />} />
|
|
<Route path="/mentions-legales" element={<MentionsLegales />} />
|
|
<Route path="/admin" element={<AdminRoute user={authState.user}><Admin /></AdminRoute>} />
|
|
<Route path="*" element={<Navigate to="/" />} />
|
|
</Routes>
|
|
</main>
|
|
</div>
|
|
<Footer onNavClick={handleNavClick} />
|
|
<CookieBanner />
|
|
<BackToTop />
|
|
</div>
|
|
);
|
|
} |