Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 104 additions & 40 deletions client/src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,110 @@
import React from 'react';
import { useState } from "react";

const Header = () => {
return (
<header className="fixed top-0 left-0 w-full bg-yellow-400 border-b-4 border-black p-5 flex justify-between items-center
shadow-[8px_8px_0px_#000] z-50">
{/* Titre animé */}
<h1 className="text-4xl font-extrabold text-white drop-shadow-[4px_4px_0px_#000] animate-wiggle">
🍢 KEBABZ
</h1>
const [isOpen, setIsOpen] = useState(false);

{/* Navigation */}
<nav>
<ul className="flex space-x-6">
<li>
<a href="/"
className="bg-red-500 text-white px-4 py-2 rounded-xl border-4 border-black
shadow-[4px_4px_0px_#000] hover:translate-x-[2px] hover:translate-y-[2px]
hover:shadow-[2px_2px_0px_#000] transition-all duration-200">
🏠 Home
</a>
</li>
<li>
<a href="/about"
className="bg-blue-500 text-white px-4 py-2 rounded-xl border-4 border-black
shadow-[4px_4px_0px_#000] hover:translate-x-[2px] hover:translate-y-[2px]
hover:shadow-[2px_2px_0px_#000] transition-all duration-200">
ℹ️ About
</a>
</li>
<li>
<a href="/restaurants"
className="bg-green-500 text-white px-4 py-2 rounded-xl border-4 border-black
shadow-[4px_4px_0px_#000] hover:translate-x-[2px] hover:translate-y-[2px]
hover:shadow-[2px_2px_0px_#000] transition-all duration-200">
🌍 Kebabiers
</a>
</li>
</ul>
</nav>
</header>
);
return (
<header
className="
fixed top-0 left-0 w-full bg-yellow-400 border-b-4 border-black
p-4 flex items-center justify-between
shadow-[8px_8px_0px_#000] z-50
"
>
{/* Titre animé */}
<h1 className="
text-2xl sm:text-3xl md:text-4xl
font-extrabold text-white
drop-shadow-[4px_4px_0px_#000]
animate-wiggle
">
🍢 KEBABZ
</h1>

{/* Bouton Hamburger visible en mobile */}
<button
className="
md:hidden text-white
text-2xl p-2 border-4 border-black rounded-xl
bg-black/30
shadow-[4px_4px_0px_#000]
"
onClick={() => setIsOpen(!isOpen)}
aria-label="Toggle menu"
>
{isOpen ? "✕" : "☰"}
</button>

{/* Navigation : masquée sur petit écran, visible à partir de md */}
<nav
className={`
absolute md:static top-[70px] left-0 w-full md:w-auto
bg-yellow-400 md:bg-transparent
border-black md:border-0 border-t-4
transition-all duration-300
${isOpen ? "block" : "hidden"} md:block
`}
>
<ul
className="
flex flex-col md:flex-row
items-start md:items-center
md:space-x-6
p-4 md:p-0
"
>
<li className="mb-2 md:mb-0">
<a
href="/"
className="
bg-red-500 text-white px-4 py-2
rounded-xl border-4 border-black
shadow-[4px_4px_0px_#000]
hover:translate-x-[2px] hover:translate-y-[2px]
hover:shadow-[2px_2px_0px_#000]
transition-all duration-200
block
"
>
🏠 Home
</a>
</li>
<li className="mb-2 md:mb-0">
<a
href="/about"
className="
bg-blue-500 text-white px-4 py-2
rounded-xl border-4 border-black
shadow-[4px_4px_0px_#000]
hover:translate-x-[2px] hover:translate-y-[2px]
hover:shadow-[2px_2px_0px_#000]
transition-all duration-200
block
"
>
ℹ️ About
</a>
</li>
<li>
<a
href="/restaurants"
className="
bg-green-500 text-white px-4 py-2
rounded-xl border-4 border-black
shadow-[4px_4px_0px_#000]
hover:translate-x-[2px] hover:translate-y-[2px]
hover:shadow-[2px_2px_0px_#000]
transition-all duration-200
block
"
>
🌍 Kebabiers
</a>
</li>
</ul>
</nav>
</header>
);
};

export default Header;

1 change: 0 additions & 1 deletion client/src/components/Loader.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from "react"
import Lottie from "lottie-react";
import burgerLoader from "../assets/chicken.json";

Expand Down
152 changes: 93 additions & 59 deletions client/src/components/RestaurantsList.jsx
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le but du localStorage est de garder le contenu de la searchBar après un reload de la page, hors ici, rien ne permet d'avoir cette fonctionnalité.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';

// SERVICES
import { getAllRestaurants } from '../services/restaurantsService';
Expand All @@ -9,71 +9,105 @@ import Loader from './Loader';
import SearchBar from "./SearchBar";

const RestaurantsList = () => {
const [restaurants, setRestaurants] = useState([]);
const [filteredRestaurants, setFilteredRestaurants] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [restaurants, setRestaurants] = useState([]);
const [filteredRestaurants, setFilteredRestaurants] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
const fetchRestaurants = async () => {
const controller = new AbortController(); // Protection contre les fuites mémoire
try {
const response = await getAllRestaurants();
setRestaurants(response.data);
setFilteredRestaurants(response.data);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Erreur lors de la récupération des restaurants:', error);
setError('Impossible de charger les restaurants. Réessayez plus tard.');
}
}

setLoading(false);
return () => controller.abort();
};

fetchRestaurants();
}, []);
useEffect(() => {
const savedSearchTerm = localStorage.getItem("searchTerm");
if (savedSearchTerm) {
setSearchTerm(savedSearchTerm);
}
}, []);

// Debounce pour éviter un filtrage à chaque frappe
useEffect(() => {
const timer = setTimeout(() => {
const filtered = restaurants.filter((restaurant) =>
JSON.stringify(restaurant).toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredRestaurants(filtered);
}, 200); // Délai de 300ms
useEffect(() => {
const fetchRestaurants = async () => {
const controller = new AbortController(); // Protection contre les fuites mémoire
try {
const response = await getAllRestaurants();
setRestaurants(response.data);
setFilteredRestaurants(response.data);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Erreur lors de la récupération des restaurants:', error);
setError('Impossible de charger les restaurants. Réessayez plus tard.');
}
}

return () => clearTimeout(timer);
}, [searchTerm, restaurants]);
setLoading(false);
return () => controller.abort();
};

return (
<div className="flex flex-col items-center justify-center min-h-screen text-white p-6">
<h1 className="text-5xl font-extrabold mb-6 drop-shadow-[4px_4px_0px_#000]">🍢 KEBABS PRÈS DE CHEZ TOI 🍢</h1>
fetchRestaurants();
}, []);

{/* Barre de recherche stylisée */}
<SearchBar value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />
// Debounce pour éviter un filtrage à chaque frappe
useEffect(() => {
const timer = setTimeout(() => {
const filtered = restaurants.filter((restaurant) =>
JSON.stringify(restaurant).toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredRestaurants(filtered);
}, 200);

{loading ? (
<Loader />
) : error ? (
<p className="text-lg text-red-500 bg-white shadow-md p-4 rounded-lg border-4 border-black">
{error}
</p>
return () => clearTimeout(timer);
}, [searchTerm, restaurants]);

return (
<div className="min-h-screen text-white p-6">
{/* Conteneur pour centrer le contenu et limiter la largeur sur grand écran */}
<div className="max-w-screen-xl mx-auto flex flex-col items-center justify-start">
<h1 className="
text-3xl sm:text-4xl md:text-5xl
font-extrabold mb-6
drop-shadow-[4px_4px_0px_#000]
text-center
">
🍢 KEBABS PRÈS DE CHEZ TOI 🍢
</h1>

{/* Barre de recherche stylisée */}
<SearchBar
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>

{loading ? (
<Loader />
) : error ? (
<p className="
mt-6 text-lg text-red-500
bg-white shadow-md p-4 rounded-lg
border-4 border-black
">
{error}
</p>
) : (
<div className="
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On peut améliorer la version mobile (centrer la grid)
image

w-full mt-7
grid gap-7
grid-cols-1
sm:grid-cols-2
md:grid-cols-3
xl:grid-cols-4
animate-fadeIn
">
{filteredRestaurants.length > 0 ? (
filteredRestaurants.map((restaurant) => (
<Card key={restaurant.id} restaurant={restaurant} />
))
) : (
<div className="flex flex-wrap justify-center animate-fadeIn">
{filteredRestaurants.length > 0 ? (
filteredRestaurants.map((restaurant) => (
<Card key={restaurant.id} restaurant={restaurant} />
))
) : (
<p className="text-gray-800 font-bold text-xl mt-4">Aucun kebab trouvé... 😢</p>
)}
</div>
<p className="text-gray-200 font-bold text-xl col-span-full text-center">
Aucun kebab trouvé... 😢
</p>
)}
</div>
);
}
</div>
)}
</div>
</div>
);
};

export default RestaurantsList;
8 changes: 7 additions & 1 deletion client/src/components/SearchBar.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from "react";
import { Search } from "lucide-react";
import PropTypes from 'prop-types';

const SearchBar = ({ value, onChange }) => {
return (
// Barre de recherche stylisée
<div className="relative w-full max-w-lg mb-6">
{/* Input stylisé */}
<input
Expand All @@ -24,6 +25,11 @@ const SearchBar = ({ value, onChange }) => {
/>
</div>
);

};
SearchBar.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
};

export default SearchBar;