diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index f1785b7..667a52c 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,4 +1,4 @@
-import { BrowserRouter, Routes, Route } from 'react-router-dom'
+import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import './App.css'
import Register from './pages/Auth/Register'
import Login from './pages/Auth/Login'
@@ -9,25 +9,37 @@ import Profile from './pages/Profile'
import Home from './pages/Home'
import { MainLayout } from './layouts/MainLayout'
import RecipeForm from "@/pages/Recipes/RecipeForm"
+import useAuth from '@/hooks/useAuth'
+import { ProtectedRoute, AuthRoute } from '@/components/RouteGuards'
function App() {
+ const { isAuthenticated, isLoading } = useAuth();
+
+ if (isLoading) {
+ return
Chargement de l'application...
;
+ }
+
return (
- {/* Pages d'authentification */}
- } />
- } />
+ {/* Routes d'authentification */}
+ } />
+ } />
- {/* Pages de recettes */}
- } />
- } />
- } />
- {/* } /> */}
+ {/* Routes publiques */}
+ } />
+ } />
- {/* Autres pages */}
- } />
- } />
+ {/* Routes protégées */}
+ } />
+ } />
+
+ {/* Route racine avec redirection conditionnelle */}
+ : } />
+
+ {/* Route de fallback pour les URLs non trouvées */}
+ } />
diff --git a/frontend/src/api/recipe.ts b/frontend/src/api/recipe.ts
index 7382d91..b08dd43 100644
--- a/frontend/src/api/recipe.ts
+++ b/frontend/src/api/recipe.ts
@@ -3,10 +3,11 @@ import axios from 'axios';
export interface Recipe {
id: string;
- title: string;
- ingredients: string;
- generatedRecipe: string;
- createdAt: string;
+ title?: string;
+ instructions: string[];
+ ingredients?: string;
+ generatedRecipe?: string;
+ createdAt?: string;
audioUrl?: string;
}
diff --git a/frontend/src/components/RouteGuards.tsx b/frontend/src/components/RouteGuards.tsx
new file mode 100644
index 0000000..7f70c4f
--- /dev/null
+++ b/frontend/src/components/RouteGuards.tsx
@@ -0,0 +1,52 @@
+import { Navigate } from 'react-router-dom';
+import useAuth from '@/hooks/useAuth';
+
+interface RouteGuardProps {
+ children: JSX.Element;
+}
+
+/**
+ * Protège les routes qui nécessitent une authentification
+ *
+ * Règles:
+ * 1. Si l'utilisateur n'est pas authentifié, redirection vers /auth/login
+ * 2. Si l'utilisateur est authentifié, affiche le composant enfant
+ */
+export const ProtectedRoute = ({ children }: RouteGuardProps): JSX.Element => {
+ const { isAuthenticated, isLoading } = useAuth();
+
+ // Afficher un loader pendant la vérification
+ if (isLoading) {
+ return Chargement...
;
+ }
+
+ // Rediriger vers login si non authentifié
+ if (!isAuthenticated) {
+ return ;
+ }
+
+ return children;
+};
+
+/**
+ * Protège les routes d'authentification (login, register)
+ *
+ * Règles:
+ * 1. Si l'utilisateur est déjà authentifié, redirection vers /recipes
+ * 2. Si l'utilisateur n'est pas authentifié, affiche le composant enfant
+ */
+export const AuthRoute = ({ children }: RouteGuardProps): JSX.Element => {
+ const { isAuthenticated, isLoading } = useAuth();
+
+ // Afficher un loader pendant la vérification
+ if (isLoading) {
+ return Chargement...
;
+ }
+
+ // Rediriger vers recipes si déjà authentifié
+ if (isAuthenticated) {
+ return ;
+ }
+
+ return children;
+};
\ No newline at end of file
diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx
index addef56..c4428b0 100644
--- a/frontend/src/components/header.tsx
+++ b/frontend/src/components/header.tsx
@@ -7,20 +7,13 @@ import { Menu, X, LogOut, User, Heart, Home, BookOpen } from "lucide-react"
import { cn } from "@/lib/utils"
import { motion, AnimatePresence } from "framer-motion"
import { Logo } from "@/components/illustrations/Logo"
+import useAuth from "@/hooks/useAuth"
export function Header() {
- const [isAuthenticated, setIsAuthenticated] = useState(false)
+ const { isAuthenticated } = useAuth()
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const location = useLocation()
- useEffect(() => {
- // Vérifier si l'utilisateur est authentifié
- const token = localStorage.getItem("token")
- setIsAuthenticated(!!token)
-
- // Fermer le menu mobile lors d'un changement de route
- setIsMobileMenuOpen(false)
- }, [location])
const toggleMobileMenu = () => {
setIsMobileMenuOpen(!isMobileMenuOpen)
@@ -28,7 +21,6 @@ export function Header() {
const handleLogout = () => {
localStorage.removeItem("token")
- setIsAuthenticated(false)
setIsMobileMenuOpen(false)
window.location.href = "/auth/login"
}
@@ -36,9 +28,9 @@ export function Header() {
const navItems = [
{ name: "Accueil", path: "/", icon: Home, public: true },
{ name: "Recettes", path: "/recipes", icon: BookOpen, public: true },
- // { name: "Mes recettes", path: "/my-recipes", icon: BookOpen, public: false },
- // { name: "Favoris", path: "/favorites", icon: Heart, public: false },
- // { name: "Profil", path: "/profile", icon: User, public: false },
+ { name: "Mes recettes", path: "/recipes", icon: BookOpen, public: false },
+ { name: "Favoris", path: "/favorites", icon: Heart, public: false },
+ { name: "Profil", path: "/profile", icon: User, public: false },
]
const filteredNavItems = navItems.filter((item) => item.public || isAuthenticated)
@@ -54,21 +46,25 @@ export function Header() {
{/* Navigation desktop */}
-
+ {
+ isAuthenticated && (
+
+ )
+ }
{/* Boutons d'authentification */}
@@ -98,80 +94,84 @@ export function Header() {
{/* Bouton menu mobile */}
-
+ {isAuthenticated && (
+
+ )}
{/* Menu mobile */}
-
- {isMobileMenuOpen && (
-
-
-
+
+
+ )}
+
+ )}
)
}
diff --git a/frontend/src/components/login-form.tsx b/frontend/src/components/login-form.tsx
index 350868f..2176b96 100644
--- a/frontend/src/components/login-form.tsx
+++ b/frontend/src/components/login-form.tsx
@@ -29,7 +29,7 @@ export function LoginForm({
throw new Error("Échec de la connexion")
}
- navigate("/")
+ navigate("/recipes")
} catch (err) {
console.error("Erreur de connexion:", err);
setError(err instanceof Error ? err.message : "Une erreur est survenue")
@@ -52,7 +52,7 @@ export function LoginForm({
localStorage.setItem("token", response.token)
- navigate("/")
+ navigate("/recipes")
} catch (err) {
console.error("Erreur de connexion Google:", err)
setError(err instanceof Error ? err.message : "Une erreur est survenue")
diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts
new file mode 100644
index 0000000..9cbe7fb
--- /dev/null
+++ b/frontend/src/hooks/useAuth.ts
@@ -0,0 +1,41 @@
+import { useState, useEffect } from 'react';
+
+interface AuthHook {
+ isAuthenticated: boolean;
+ isLoading: boolean;
+ checkAuth: () => boolean;
+}
+
+/**
+ * Hook pour gérer l'authentification
+ *
+ * Règles:
+ * 1. Un utilisateur est considéré comme authentifié s'il a un token valide dans localStorage
+ * 2. Le hook vérifie l'authentification au chargement et fournit un état de chargement
+ * 3. Expose une méthode pour vérifier l'authentification à la demande
+ */
+const useAuth = (): AuthHook => {
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const [isLoading, setIsLoading] = useState(true);
+
+ // Fonction pour vérifier l'authentification
+ const checkAuth = (): boolean => {
+ const token = localStorage.getItem('token');
+ // On pourrait ajouter une vérification de validité du token ici
+ return !!token;
+ };
+
+ useEffect(() => {
+ // Vérifier l'authentification au chargement
+ setIsAuthenticated(checkAuth());
+ setIsLoading(false);
+ }, []);
+
+ return {
+ isAuthenticated,
+ isLoading,
+ checkAuth
+ };
+};
+
+export default useAuth;
\ No newline at end of file
diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx
index 8756ac5..ca77113 100644
--- a/frontend/src/pages/Home.tsx
+++ b/frontend/src/pages/Home.tsx
@@ -13,7 +13,6 @@ import PastaVongole from "@/assets/pasta-alla-vongole-home.jpeg"
export default function Home() {
return (
- 222
@@ -37,20 +36,20 @@ export default function Home() {
secondes. Fini le gaspillage alimentaire !
-
+
-
+ {/*
-
+ */}
-
+ {/*
-
+ */}
diff --git a/frontend/src/pages/Recipes/RecipeDetail.tsx b/frontend/src/pages/Recipes/RecipeDetail.tsx
index 54bd5d4..5c7122c 100644
--- a/frontend/src/pages/Recipes/RecipeDetail.tsx
+++ b/frontend/src/pages/Recipes/RecipeDetail.tsx
@@ -11,8 +11,6 @@ import {
Heart,
Share2,
ArrowLeft,
- Trash2,
- Edit,
Printer,
CheckCircle2,
Timer,
@@ -32,8 +30,8 @@ export default function RecipeDetail() {
const [recipe, setRecipe] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState("")
- const [isFavorite, setIsFavorite] = useState(false)
- const [addingToFavorites, setAddingToFavorites] = useState(false)
+ // const [isFavorite, setIsFavorite] = useState(false)
+ // const [addingToFavorites, setAddingToFavorites] = useState(false)
const [checkedIngredients, setCheckedIngredients] = useState>(new Set())
const [checkedSteps, setCheckedSteps] = useState>(new Set())
const [showScrollTop, setShowScrollTop] = useState(false)
@@ -68,8 +66,8 @@ export default function RecipeDetail() {
// ✅ GET FAVORITE RECIPES & CHECK IF FAVORITE
try {
- const favorites = await recipeService.getFavoriteRecipes()
- setIsFavorite(favorites.some((fav) => fav.id === id))
+ // const favorites = await recipeService.getFavoriteRecipes()
+ // setIsFavorite(favorites.some((fav) => fav.id === id))
} catch (favError) {
// Ignorer les erreurs de favoris pour ne pas bloquer l'affichage
console.log("Impossible de vérifier les favoris:", favError)
@@ -97,42 +95,42 @@ export default function RecipeDetail() {
return () => window.removeEventListener("scroll", handleScroll)
}, [id])
- const handleToggleFavorite = async () => {
- if (!id || !recipe) return
+ // const handleToggleFavorite = async () => {
+ // if (!id || !recipe) return
- try {
- setAddingToFavorites(true)
+ // try {
+ // setAddingToFavorites(true)
- if (isFavorite) {
- // ✅ REMOVE FROM FAVORITES
- await recipeService.removeFromFavorites(id)
- setIsFavorite(false)
- } else {
- // ✅ ADD TO FAVORITES
- await recipeService.addToFavorites(id)
- setIsFavorite(true)
- }
- } catch (err) {
- console.error("Erreur lors de la modification des favoris:", err)
- // Ne pas afficher d'erreur à l'utilisateur pour cette fonctionnalité
- } finally {
- setAddingToFavorites(false)
- }
- }
+ // if (isFavorite) {
+ // // ✅ REMOVE FROM FAVORITES
+ // await recipeService.removeFromFavorites(id)
+ // setIsFavorite(false)
+ // } else {
+ // // ✅ ADD TO FAVORITES
+ // await recipeService.addToFavorites(id)
+ // setIsFavorite(true)
+ // }
+ // } catch (err) {
+ // console.error("Erreur lors de la modification des favoris:", err)
+ // // Ne pas afficher d'erreur à l'utilisateur pour cette fonctionnalité
+ // } finally {
+ // setAddingToFavorites(false)
+ // }
+ // }
- const handleDeleteRecipe = async () => {
- if (!id) return
- if (!window.confirm("Êtes-vous sûr de vouloir supprimer cette recette ?")) return
+ // const handleDeleteRecipe = async () => {
+ // if (!id) return
+ // if (!window.confirm("Êtes-vous sûr de vouloir supprimer cette recette ?")) return
- try {
- // ✅ DELETE RECIPE
- await recipeService.deleteRecipe(id)
- navigate("/recipes")
- } catch (err) {
- console.error("Erreur lors de la suppression:", err)
- // Optionnel: afficher un message d'erreur à l'utilisateur
- }
- }
+ // try {
+ // // ✅ DELETE RECIPE
+ // await recipeService.deleteRecipe(id)
+ // navigate("/recipes")
+ // } catch (err) {
+ // console.error("Erreur lors de la suppression:", err)
+ // // Optionnel: afficher un message d'erreur à l'utilisateur
+ // }
+ // }
const handleShare = () => {
if (!recipe) return
@@ -177,9 +175,6 @@ export default function RecipeDetail() {
window.scrollTo({ top: 0, behavior: "smooth" })
}
- // ========================================
- // === LOADING & ERROR STATES ============
- // ========================================
if (loading) {
return
}
@@ -222,7 +217,7 @@ export default function RecipeDetail() {
-
+ {/*
+ */}
@@ -371,41 +366,33 @@ export default function RecipeDetail() {
{recipe.instructions && recipe.instructions.length > 0 ? (
recipe.instructions.map((instruction, index) => (
- toggleStep(index)}
className="mr-3 mt-1 data-[state=checked]:bg-orange-500 data-[state=checked]:border-orange-500"
- />
+ /> */}
-
-
{instruction}
+
+ {instruction}
+ {checkedSteps.has(index) && }
+
))
) : (
- toggleStep(0)}
className="mr-3 mt-1 data-[state=checked]:bg-orange-500 data-[state=checked]:border-orange-500"
- />
+ /> */}
-
-
{recipe.generatedRecipe}
+
+ {recipe.generatedRecipe}
+ {checkedSteps.has(0) && }
+
)}