-
-
-
+
+
+
+
+
+
+
Dites adieu au gaspillage alimentaire
+
+ Notre IA analyse les ingrédients que vous avez déjà et vous propose des recettes adaptées, vous
+ permettant d'utiliser ce que vous avez sous la main.
+
+
+
+
+
+
+
+
+
+
Dictez vos ingrédients
+
+ Énumérez simplement ce que vous avez dans votre frigo
+
+
+
+
+
+
+
+
+
+
Scannez votre frigo
+
+ Prenez une photo et notre IA identifie vos ingrédients
+
+
+
+
+
+
+
+
+
+
Recettes personnalisées
+
+ Obtenez des suggestions adaptées à vos préférences et restrictions
+
+
+
+
+
+
+
+
- Recherche intelligente
-
- Trouvez des recettes en fonction des ingrédients que vous avez déjà chez vous.
-
-
-
-
-
+
+
+
+
- Créez et partagez
-
- Ajoutez vos propres recettes et partagez-les avec la communauté.
-
-
-
-
-
-
-
Favoris personnalisés
-
- Enregistrez vos recettes préférées pour y accéder rapidement.
-
-
+
+
+ {/* How it works section */}
+
+
+
+
+
Comment ça marche
+
+ En trois étapes simples, transformez les ingrédients de votre frigo en délicieux repas
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Pricing section */}
+
+
+
+
+
Plans d'abonnement
+
+ Choisissez le plan qui correspond à vos besoins culinaires
+
+
+
+
+
+
+
+
+ {/* Testimonials section */}
+
+
+
+
+
+ Ce que disent nos utilisateurs
+
+
+ Découvrez comment Freedge transforme la façon dont les gens cuisinent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* CTA section */}
+
+
+
+
+
+
+
+ Prêt à transformer votre façon de cuisiner ?
+
+
+ Rejoignez des milliers d'utilisateurs qui économisent du temps, de l'argent et réduisent leur gaspillage
+ alimentaire grâce à Freedge.
+
+
+
+
+
+
+
+
+
+
+
+
+
- );
-}
\ No newline at end of file
+ )
+}
+
diff --git a/frontend/src/pages/Recipes/RecipeDetail.tsx b/frontend/src/pages/Recipes/RecipeDetail.tsx
index 2a8d5e2..70f1e52 100644
--- a/frontend/src/pages/Recipes/RecipeDetail.tsx
+++ b/frontend/src/pages/Recipes/RecipeDetail.tsx
@@ -1,7 +1,9 @@
-import { useState, useEffect } from "react";
-import { useParams, useNavigate } from "react-router-dom";
-import { recipeService, Recipe } from "@/api/recipe";
-import { Button } from "@/components/ui/button";
+"use client"
+
+import { useState, useEffect, useRef } from "react"
+import { useParams, useNavigate } from "react-router-dom"
+import { recipeService, type Recipe } from "@/api/recipe"
+import { Button } from "@/components/ui/button"
import {
Clock,
Users,
@@ -11,132 +13,175 @@ import {
ArrowLeft,
Trash2,
Edit,
- HeartOff
-} from "lucide-react";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+ Printer,
+ CheckCircle2,
+ Timer,
+ ChevronUp,
+} from "lucide-react"
+import { motion, AnimatePresence } from "framer-motion"
+import { RecipeDetailLoader } from "@/components/illustrations/RecipeDetailLoader"
+import { Badge } from "@/components/ui/badge"
+import { Card, CardContent } from "@/components/ui/card"
+import { Checkbox } from "@/components/ui/checkbox"
export default function RecipeDetail() {
- const { id } = useParams<{ id: string }>();
- const navigate = useNavigate();
+ const { id } = useParams<{ id: string }>()
+ const navigate = useNavigate()
+ const contentRef = useRef
(null)
- 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 [recipe, setRecipe] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState("")
+ 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)
useEffect(() => {
const fetchRecipeDetails = async () => {
- if (!id) return;
+ if (!id) return
try {
- setLoading(true);
+ setLoading(true)
// ✅ GET RECIPE DETAILS
- const recipeData = await recipeService.getRecipeById(id);
+ const recipeData = await recipeService.getRecipeById(id)
// Optionnel : conversion ingrédients / instructions si nécessaire
- let ingredients = recipeData.ingredients;
+ let ingredients = recipeData.ingredients
if (typeof ingredients === "string") {
- ingredients = ingredients.split("\n").filter(item => item.trim() !== "");
+ ingredients = ingredients.split("\n").filter((item) => item.trim() !== "")
}
// Correction: déclarer la variable instructions
- let instructions: string[] = [];
+ let instructions: string[] = []
if (recipeData.generatedRecipe) {
- instructions = recipeData.generatedRecipe
- .split("\n")
- .filter(item => item.trim() !== "");
+ instructions = recipeData.generatedRecipe.split("\n").filter((item) => item.trim() !== "")
}
setRecipe({
...recipeData,
ingredients,
instructions, // Ajouter instructions au recipe
- });
+ })
// ✅ 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);
+ console.log("Impossible de vérifier les favoris:", favError)
}
-
} catch (err) {
- console.error(err);
- setError("Impossible de charger les détails de la recette");
+ console.error(err)
+ setError("Impossible de charger les détails de la recette")
} finally {
- setLoading(false);
+ setLoading(false)
}
- };
+ }
- fetchRecipeDetails();
- }, [id]);
+ fetchRecipeDetails()
+
+ // Setup scroll listener
+ const handleScroll = () => {
+ if (window.scrollY > 300) {
+ setShowScrollTop(true)
+ } else {
+ setShowScrollTop(false)
+ }
+ }
+
+ window.addEventListener("scroll", handleScroll)
+ return () => window.removeEventListener("scroll", handleScroll)
+ }, [id])
const handleToggleFavorite = async () => {
- if (!id || !recipe) return;
+ if (!id || !recipe) return
try {
- setAddingToFavorites(true);
+ setAddingToFavorites(true)
if (isFavorite) {
// ✅ REMOVE FROM FAVORITES
- await recipeService.removeFromFavorites(id);
- setIsFavorite(false);
+ await recipeService.removeFromFavorites(id)
+ setIsFavorite(false)
} else {
// ✅ ADD TO FAVORITES
- await recipeService.addToFavorites(id);
- setIsFavorite(true);
+ await recipeService.addToFavorites(id)
+ setIsFavorite(true)
}
-
} catch (err) {
- console.error("Erreur lors de la modification des favoris:", err);
+ console.error("Erreur lors de la modification des favoris:", err)
// Ne pas afficher d'erreur à l'utilisateur pour cette fonctionnalité
} finally {
- setAddingToFavorites(false);
+ setAddingToFavorites(false)
}
- };
+ }
const handleDeleteRecipe = async () => {
- if (!id) return;
- if (!window.confirm("Êtes-vous sûr de vouloir supprimer cette recette ?")) return;
+ 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");
+ await recipeService.deleteRecipe(id)
+ navigate("/recipes")
} catch (err) {
- console.error("Erreur lors de la suppression:", err);
+ console.error("Erreur lors de la suppression:", err)
// Optionnel: afficher un message d'erreur à l'utilisateur
}
- };
+ }
const handleShare = () => {
- if (!recipe) return;
+ if (!recipe) return
if (navigator.share) {
navigator.share({
title: recipe.title,
text: recipe.description || "Découvrez cette recette !",
url: window.location.href,
- });
+ })
} else {
- navigator.clipboard.writeText(window.location.href);
- alert("Lien copié dans le presse-papier !");
+ navigator.clipboard.writeText(window.location.href)
+ alert("Lien copié dans le presse-papier !")
}
- };
+ }
+
+ const handlePrint = () => {
+ window.print()
+ }
+
+ const toggleIngredient = (index: number) => {
+ const newChecked = new Set(checkedIngredients)
+ if (newChecked.has(index)) {
+ newChecked.delete(index)
+ } else {
+ newChecked.add(index)
+ }
+ setCheckedIngredients(newChecked)
+ }
+
+ const toggleStep = (index: number) => {
+ const newChecked = new Set(checkedSteps)
+ if (newChecked.has(index)) {
+ newChecked.delete(index)
+ } else {
+ newChecked.add(index)
+ }
+ setCheckedSteps(newChecked)
+ }
+
+ const scrollToTop = () => {
+ window.scrollTo({ top: 0, behavior: "smooth" })
+ }
// ========================================
// === LOADING & ERROR STATES ============
// ========================================
if (loading) {
- return (
-
- );
+ return
}
if (error || !recipe) {
@@ -144,195 +189,268 @@ export default function RecipeDetail() {
Erreur
{error || "Recette introuvable"}
-
- );
+ )
}
+ // Calculate total time
+ const totalTime = (recipe.preparationTime || 0) + (recipe.cookingTime || 0)
+
// ========================================
// === MAIN COMPONENT =====================
// ========================================
return (
-
-
-
navigate("/recipes")}
- >
-
- Retour aux recettes
-
+
+ {/* Header */}
+
+
+
+
navigate("/recipes")}>
+
+ Retour aux recettes
+
-
-
-
- Partager
-
+
+
+
+
-
- {isFavorite ? (
- <>
-
- Retirer des favoris
- >
- ) : (
- <>
-
- Ajouter aux favoris
- >
- )}
-
+
+
+
-
navigate(`/recipes/edit/${id}`)}
- >
-
- Modifier
-
-
-
-
- Supprimer
-
-
-
-
-
-
-
-

-
-
-
-
-
{recipe.title}
-
-
- {recipe.description || "Aucune description disponible"}
-
-
-
- {recipe.tags?.map((tag) => (
-
- {tag}
-
- ))}
-
-
-
- {recipe.preparationTime && (
-
-
- Préparation
- {recipe.preparationTime} min
-
- )}
-
- {recipe.cookingTime && (
-
-
- Cuisson
- {recipe.cookingTime} min
-
- )}
-
- {recipe.servings && (
-
-
- Portions
- {recipe.servings}
-
- )}
-
- {recipe.difficulty && (
-
-
- Difficulté
- {recipe.difficulty}
-
- )}
+
+
+
-
+
-
-
- Ingrédients
- Instructions
-
-
-
-
-
Ingrédients
-
- {Array.isArray(recipe.ingredients) ? (
- recipe.ingredients.map((ingredient, index) => (
- -
-
- {ingredient}
-
- ))
- ) : (
- -
-
- {recipe.ingredients}
-
- )}
-
+ {/* Main content */}
+
+
+ {/* Recipe Hero */}
+
+

+
+
+ {recipe.tags?.map((tag) => (
+
+ {tag}
+
+ ))}
+
+
{recipe.title}
+
{recipe.description || "Aucune description disponible"}
+
-
-
-
-
Instructions
-
- {recipe.instructions && recipe.instructions.length > 0 ? (
- recipe.instructions.map((instruction, index) => (
- -
-
- {index + 1}
+ {/* Recipe Info Cards */}
+
+
+
+
+ Préparation
+ {recipe.preparationTime || 0} min
+
+
+
+
+
+
+ Cuisson
+ {recipe.cookingTime || 0} min
+
+
+
+
+
+
+ Portions
+ {recipe.servings || "-"}
+
+
+
+
+
+
+ Difficulté
+ {recipe.difficulty || "-"}
+
+
+
+
+ {/* Recipe Content */}
+
+ {/* Ingredients */}
+
+
+
+
+
+ 1
-
{instruction}
-
- ))
- ) : (
- -
-
- 1
-
-
{recipe.generatedRecipe}
-
- )}
-
+ Ingrédients
+
+
+ {totalTime > 0 && (
+
+
+
+ Temps total: {totalTime} min
+
+
+ )}
+
+
+ {Array.isArray(recipe.ingredients) ? (
+ recipe.ingredients.map((ingredient, index) => (
+ -
+ toggleIngredient(index)}
+ className="mr-2 data-[state=checked]:bg-orange-500 data-[state=checked]:border-orange-500"
+ />
+
+
+ ))
+ ) : (
+ -
+ toggleIngredient(0)}
+ className="mr-2 data-[state=checked]:bg-orange-500 data-[state=checked]:border-orange-500"
+ />
+
+
+ )}
+
+
+
+
+
+ {/* Instructions */}
+
+
+
+
+
+ 2
+
+ Instructions
+
+
+
+ {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}
+
+
+ ))
+ ) : (
+ -
+ toggleStep(0)}
+ className="mr-3 mt-1 data-[state=checked]:bg-orange-500 data-[state=checked]:border-orange-500"
+ />
+
+
+
{recipe.generatedRecipe}
+
+
+ )}
+
+
+
+
-
-
+
+ {/* Admin Actions */}
+
+ navigate(`/recipes/edit/${id}`)}>
+
+ Modifier
+
+
+
+
+ Supprimer
+
+
+
+
+
+ {/* Scroll to top button */}
+
+ {showScrollTop && (
+
+
+
+
+
+ )}
+
- );
+ )
}
+
diff --git a/frontend/src/pages/Recipes/RecipeForm.tsx b/frontend/src/pages/Recipes/RecipeForm.tsx
index ae0674a..b74f981 100644
--- a/frontend/src/pages/Recipes/RecipeForm.tsx
+++ b/frontend/src/pages/Recipes/RecipeForm.tsx
@@ -99,8 +99,8 @@ export default function RecipeForm() {
}
return (
-
-
+
+
-
+
{/* Illustrations */}
-
-
+
+
+
+ Notre intelligence artificielle transforme votre façon de cuisiner
+
-
- Créer une nouvelle recette
-
+
+ Créer une nouvelle recette
+
Enregistrez ou téléchargez un fichier audio listant les ingrédients disponibles, et nous générerons une
recette pour vous.
-
+
{error && (
{error}
@@ -151,34 +156,6 @@ export default function RecipeForm() {
)}
-
-
- fileInputRef.current?.click()}
- disabled={isRecording}
- >
-
- Choisir un fichier
-
-
-
-
- {isRecording ? "Arrêter" : "Enregistrer"}
-
-
-
Enregistrez-vous en listant les ingrédients que vous avez à disposition. Notre IA générera une
recette adaptée à ces ingrédients.
@@ -197,7 +174,7 @@ export default function RecipeForm() {
)}
-
+
-
- {loading ? (
- <>
-
- Création en cours...
- >
- ) : (
- <>
-
- Créer la recette
- >
- )}
-
+ {!audioFile && !isRecording && recordingStatus !== "processing" && !loading && (
+
+
+ Commencer l'enregistrement
+
+ )}
+
+ {isRecording && (
+
+
+
+ Arrêter l'enregistrement
+
+
+ )}
+
+ {audioFile && !isRecording && recordingStatus !== "processing" && !loading && (
+
+
+ Créer la recette
+
+ )}
+
+ {loading && (
+
+
+ Création en cours...
+
+ )}
-
- {/* Recording button at bottom */}
- {recordingStatus !== "processing" && !loading && !audioFile && (
-
-
-
- {isRecording ? (
-
-
-
- ) : (
-
- )}
-
-
-
- )}
-
)
}
diff --git a/frontend/src/pages/Recipes/RecipeList.tsx b/frontend/src/pages/Recipes/RecipeList.tsx
index e98ae44..68c1679 100644
--- a/frontend/src/pages/Recipes/RecipeList.tsx
+++ b/frontend/src/pages/Recipes/RecipeList.tsx
@@ -89,7 +89,7 @@ export default function RecipeList() {
)}
-
+
Toutes
@@ -116,7 +116,7 @@ export default function RecipeList() {
) : (