RegWatch frontend : React/Vite -> nginx + Docker/CI homelab
Build & Deploy / build (push) Successful in 18s
Build & Deploy / build (push) Successful in 18s
- URL d'API externalisée (VITE_API_URL bakée au build), plus de localhost en dur - Dockerfile multi-stage pnpm build -> nginx (fallback SPA) - docker-compose (Watchtower) + .gitea/workflows/build.yml Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+163
@@ -0,0 +1,163 @@
|
||||
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 Sidebar from "./components/Sidebar";
|
||||
import Footer from "./components/Footer";
|
||||
|
||||
import styles from "./App.module.css";
|
||||
|
||||
import { getDocuments, type ApiDocument } from "./services/theapi";
|
||||
|
||||
type NavItem =
|
||||
| "home"
|
||||
| "ingredient-search"
|
||||
| "documents"
|
||||
| "recent-updates"
|
||||
| "about"
|
||||
| "contact"
|
||||
| "cgu"
|
||||
| "mentions-legales";
|
||||
|
||||
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"
|
||||
: "home";
|
||||
|
||||
// LOAD RECENT DOCUMENTS
|
||||
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"
|
||||
: "/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>
|
||||
);
|
||||
}
|
||||
|
||||
// Si pas authentifié, afficher la page de login
|
||||
if (!authState.isAuthenticated) {
|
||||
return <AuthPage onAuthSuccess={handleAuthSuccess} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.app}>
|
||||
<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="*" element={<Navigate to="/" />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
<Footer onNavClick={handleNavClick} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user