+ );
+}
diff --git a/frontend/src/components/illustrations/EmptyRecipes.tsx b/frontend/src/components/illustrations/EmptyRecipes.tsx
new file mode 100644
index 0000000..2ec0b81
--- /dev/null
+++ b/frontend/src/components/illustrations/EmptyRecipes.tsx
@@ -0,0 +1,51 @@
+"use client"
+
+import { motion } from "framer-motion"
+import { Button } from "@/components/ui/button"
+import { ChefHat, Plus } from "lucide-react"
+
+interface EmptyRecipesProps {
+ onCreateRecipe: () => void
+}
+
+export function EmptyRecipes({ onCreateRecipe }: EmptyRecipesProps) {
+ return (
+
+
+
+
+
+
+
Aucune recette trouvée
+
+ Votre collection de recettes est vide. Créez votre première recette en enregistrant les ingrédients que vous
+ avez dans votre frigo.
+
+
+
+
+ )
+}
+
diff --git a/frontend/src/components/illustrations/KitchenIllustration.tsx b/frontend/src/components/illustrations/KitchenIllustration.tsx
new file mode 100644
index 0000000..927a216
--- /dev/null
+++ b/frontend/src/components/illustrations/KitchenIllustration.tsx
@@ -0,0 +1,62 @@
+import { motion } from "framer-motion";
+
+export function KitchenIllustration() {
+ return (
+
+ );
+}
diff --git a/frontend/src/components/ui/progress.tsx b/frontend/src/components/ui/progress.tsx
new file mode 100644
index 0000000..bd761c6
--- /dev/null
+++ b/frontend/src/components/ui/progress.tsx
@@ -0,0 +1,26 @@
+import * as React from "react"
+import * as ProgressPrimitive from "@radix-ui/react-progress"
+
+import { cn } from "@/lib/utils"
+
+const Progress = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, value, ...props }, ref) => (
+
+
+
+))
+Progress.displayName = ProgressPrimitive.Root.displayName
+
+export { Progress }
\ No newline at end of file
diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx
index 46e82d7..1d9355b 100644
--- a/frontend/src/layouts/MainLayout.tsx
+++ b/frontend/src/layouts/MainLayout.tsx
@@ -17,10 +17,10 @@ export function MainLayout({ children }: MainLayoutProps) {
}
return (
-
+
-
+
{children}
diff --git a/frontend/src/pages/Recipes/RecipeForm.tsx b/frontend/src/pages/Recipes/RecipeForm.tsx
index f57b18d..ae0674a 100644
--- a/frontend/src/pages/Recipes/RecipeForm.tsx
+++ b/frontend/src/pages/Recipes/RecipeForm.tsx
@@ -1,209 +1,216 @@
-import { useState } from "react";
-import { useNavigate } from "react-router-dom";
-import { recipeService } from "@/api/recipe";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Textarea } from "@/components/ui/textarea";
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
-import { Mic, Upload, ArrowLeft, Loader2 } from "lucide-react";
+"use client"
+
+import type React from "react"
+
+import { useState, useRef } from "react"
+import { useNavigate } from "react-router-dom"
+import { recipeService } from "@/api/recipe"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
+import { Mic, Upload, ArrowLeft, Loader2 } from "lucide-react"
+import { motion } from "framer-motion"
+import { KitchenIllustration } from "@/components/illustrations/KitchenIllustration"
+import { CookingLoader } from "@/components/illustrations/CookingLoader"
export default function RecipeForm() {
- const navigate = useNavigate();
+ const navigate = useNavigate()
+ const fileInputRef = useRef(null)
- const [audioFile, setAudioFile] = useState(null);
- const [isRecording, setIsRecording] = useState(false);
- const [mediaRecorder, setMediaRecorder] = useState(null);
- const [recordingStatus, setRecordingStatus] = useState("idle");
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState("");
+ const [audioFile, setAudioFile] = useState(null)
+ const [isRecording, setIsRecording] = useState(false)
+ const [mediaRecorder, setMediaRecorder] = useState(null)
+ const [recordingStatus, setRecordingStatus] = useState<"idle" | "recording" | "processing">("idle")
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState("")
// Gérer l'upload de fichier audio
const handleFileChange = (e: React.ChangeEvent) => {
if (e.target.files && e.target.files.length > 0) {
- setAudioFile(e.target.files[0]);
+ setAudioFile(e.target.files[0])
+ setError("")
}
- };
+ }
// Démarrer l'enregistrement audio
const startRecording = async () => {
try {
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
- const recorder = new MediaRecorder(stream);
- setMediaRecorder(recorder);
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
+ const recorder = new MediaRecorder(stream)
+ setMediaRecorder(recorder)
- const chunks: BlobPart[] = [];
+ const chunks: BlobPart[] = []
recorder.ondataavailable = (e) => {
- chunks.push(e.data);
- };
+ chunks.push(e.data)
+ }
recorder.onstop = () => {
- const blob = new Blob(chunks, { type: 'audio/webm' });
- const file = new File([blob], "recording.webm", { type: 'audio/webm' });
- setAudioFile(file);
- setRecordingStatus("idle");
- };
+ const blob = new Blob(chunks, { type: "audio/webm" })
+ const file = new File([blob], "recording.webm", { type: "audio/webm" })
+ setAudioFile(file)
+ setRecordingStatus("idle")
+ setError("")
+ }
- recorder.start();
- setIsRecording(true);
- setRecordingStatus("recording");
+ recorder.start()
+ setIsRecording(true)
+ setRecordingStatus("recording")
} catch (err) {
- console.error("Erreur lors de l'accès au microphone:", err);
- // toast({
- // variant: "destructive",
- // title: "Erreur de microphone",
- // description: "Impossible d'accéder au microphone. Vérifiez les permissions."
- // });
+ console.error("Erreur lors de l'accès au microphone:", err)
+ setError("Impossible d'accéder au microphone. Vérifiez les permissions.")
}
- };
+ }
// Arrêter l'enregistrement audio
const stopRecording = () => {
if (mediaRecorder && isRecording) {
- mediaRecorder.stop();
- setIsRecording(false);
- setRecordingStatus("processing");
+ mediaRecorder.stop()
+ setIsRecording(false)
+ setRecordingStatus("processing")
// Arrêter toutes les pistes audio
- mediaRecorder.stream.getTracks().forEach(track => track.stop());
+ mediaRecorder.stream.getTracks().forEach((track) => track.stop())
}
- };
+ }
// Soumettre le formulaire
const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
+ e.preventDefault()
if (!audioFile) {
- setError("Veuillez fournir un enregistrement audio des ingrédients");
- return;
+ setError("Veuillez fournir un enregistrement audio des ingrédients")
+ return
}
- setLoading(true);
- setError("");
+ setLoading(true)
+ setError("")
+ setRecordingStatus("processing")
try {
- const recipe = await recipeService.createRecipe(audioFile);
-
- // toast({
- // title: "Recette créée !",
- // description: "Votre recette a été générée avec succès."
- // });
-
- // Rediriger vers la page de détails de la recette
- navigate(`/recipes/${recipe.id}`);
+ const recipe = await recipeService.createRecipe(audioFile)
+ navigate(`/recipes/${recipe.id}`)
} catch (err) {
- console.error("Erreur lors de la création de la recette:", err);
- setError(err instanceof Error ? err.message : "Une erreur est survenue lors de la création de la recette");
-
- // toast({
- // variant: "destructive",
- // title: "Erreur",
- // description: "Impossible de créer la recette. Veuillez réessayer."
- // });
+ console.error("Erreur lors de la création de la recette:", err)
+ setError(err instanceof Error ? err.message : "Une erreur est survenue lors de la création de la recette")
+ setRecordingStatus("idle")
} finally {
- setLoading(false);
+ setLoading(false)
}
- };
+ }
return (
-
-
+
+
+
+
-
-
- 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.
-
-
+
+ {/* Illustrations */}
+
+
+
-
+ )
+}
+
diff --git a/frontend/src/pages/Recipes/RecipeList.tsx b/frontend/src/pages/Recipes/RecipeList.tsx
index 26ea4cd..e98ae44 100644
--- a/frontend/src/pages/Recipes/RecipeList.tsx
+++ b/frontend/src/pages/Recipes/RecipeList.tsx
@@ -1,159 +1,253 @@
-import { useState, useEffect } from "react";
-import { Link, useNavigate } from "react-router-dom";
-import { recipeService, Recipe } from "@/api/recipe";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Search, Filter, Plus } from "lucide-react";
+"use client"
+
+import type React from "react"
+
+import { useState, useEffect } from "react"
+import { useNavigate } from "react-router-dom"
+import { recipeService, type Recipe } from "@/api/recipe"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Search, Plus, Clock, Utensils, Heart, Share2, ArrowUpRight } from "lucide-react"
+import { motion } from "framer-motion"
+import { CookingLoader } from "@/components/illustrations/CookingLoader"
+import { KitchenIllustration } from "@/components/illustrations/KitchenIllustration"
+import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Badge } from "@/components/ui/badge"
+import { Card, CardContent, CardFooter } from "@/components/ui/card"
+import { EmptyRecipes } from "@/components/illustrations/EmptyRecipes"
export default function RecipeList() {
- const navigate = useNavigate();
- const [recipes, setRecipes] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState("");
- const [searchQuery, setSearchQuery] = useState("");
+ const navigate = useNavigate()
+ const [recipes, setRecipes] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState("")
+ const [searchQuery, setSearchQuery] = useState("")
+ const [activeFilter, setActiveFilter] = useState("all")
useEffect(() => {
const fetchRecipes = async () => {
try {
- setLoading(true);
- const data = await recipeService.getRecipes();
- setRecipes(data);
+ setLoading(true)
+ const data = await recipeService.getRecipes()
+ setRecipes(data)
} catch (err) {
- setError("Impossible de charger les recettes");
- console.error(err);
+ setError("Impossible de charger les recettes")
+ console.error(err)
} finally {
- setLoading(false);
+ setLoading(false)
}
- };
+ }
- fetchRecipes();
- }, []);
+ fetchRecipes()
+ }, [])
const handleSearch = async (e: React.FormEvent) => {
- e.preventDefault();
+ e.preventDefault()
if (!searchQuery.trim()) {
- const data = await recipeService.getRecipes();
- setRecipes(data);
- return;
+ const data = await recipeService.getRecipes()
+ setRecipes(data)
+ return
}
try {
- setLoading(true);
- const results = await recipeService.getRecipes();
- setRecipes(results);
+ setLoading(true)
+ const results = await recipeService.getRecipes()
+ setRecipes(results)
} catch (err) {
- setError("Erreur lors de la recherche");
- console.error(err);
+ setError("Erreur lors de la recherche")
+ console.error(err)
} finally {
- setLoading(false);
+ setLoading(false)
}
- };
+ }
const handleCreateRecipe = () => {
- navigate("/recipes/new");
- };
+ navigate("/recipes/new")
+ }
+
+ // Filter recipes based on the active filter
+ const filteredRecipes = recipes.filter((recipe) => {
+ if (activeFilter === "all") return true
+ if (activeFilter === "easy" && recipe.difficulty === "Facile") return true
+ if (activeFilter === "quick" && (recipe.preparationTime || 0) <= 30) return true
+ if (activeFilter === "vegetarian" && recipe.tags?.includes("Végétarien")) return true
+ return false
+ })
return (
-
-
-
-
Recettes
-
- Découvrez notre collection de recettes délicieuses
-
-
-
-
-
-
-
+
+ {/* Main content */}
{error && (
-
+
{error}
-
+
)}
+
+
+
+ Toutes
+ Faciles
+ Rapides
+ Végé
+
+
+
+
+
+
{loading ? (
-
-
-
+
) : (
<>
- {recipes.length === 0 ? (
-
-
Aucune recette trouvée
-
- Essayez de modifier vos critères de recherche ou
-
-