Browse Source

Merge pull request #13 from Astri4-4/features/search

Features/search
fix/validators
Sacha GUERIN 4 months ago
committed by GitHub
parent
commit
9017e8a786
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      frontend/src/assets/svg/infinite.svg
  2. 4
      frontend/src/assets/svg/play.svg
  3. 1
      frontend/src/assets/svg/user.svg
  4. 21
      frontend/src/components/CreatorCard.jsx
  5. 38
      frontend/src/components/Navbar.jsx
  6. 7
      frontend/src/index.css
  7. 128
      frontend/src/pages/Search.jsx
  8. 5
      frontend/src/routes/routes.jsx
  9. 15
      frontend/src/services/search.service.js

1
frontend/src/assets/svg/infinite.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M17 7c-2.094 0-3.611 1.567-5.001 3.346C10.609 8.567 9.093 7 7 7c-2.757 0-5 2.243-5 5a4.98 4.98 0 0 0 1.459 3.534A4.956 4.956 0 0 0 6.99 17h.012c2.089-.005 3.605-1.572 4.996-3.351C13.389 15.431 14.906 17 17 17c2.757 0 5-2.243 5-5s-2.243-5-5-5zM6.998 15l-.008 1v-1c-.799 0-1.55-.312-2.114-.878A3.004 3.004 0 0 1 7 9c1.33 0 2.56 1.438 3.746 2.998C9.558 13.557 8.328 14.997 6.998 15zM17 15c-1.33 0-2.561-1.44-3.749-3.002C14.438 10.438 15.668 9 17 9c1.654 0 3 1.346 3 3s-1.346 3-3 3z"></path></svg>

After

Width:  |  Height:  |  Size: 585 B

4
frontend/src/assets/svg/play.svg

@ -1,3 +1 @@
<svg width="30" height="34" viewBox="0 0 30 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.5 14.4019C30.5 15.5566 30.5 18.4434 28.5 19.5981L4.5 33.4545C2.5 34.6092 2.14642e-06 33.1658 2.24736e-06 30.8564L3.45873e-06 3.14359C3.55968e-06 0.834193 2.5 -0.609184 4.5 0.545517L28.5 14.4019Z" fill="white"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7 6v12l10-6z"></path></svg>

Before

Width:  |  Height:  |  Size: 327 B

After

Width:  |  Height:  |  Size: 120 B

1
frontend/src/assets/svg/user.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 2a5 5 0 1 0 5 5 5 5 0 0 0-5-5zm0 8a3 3 0 1 1 3-3 3 3 0 0 1-3 3zm9 11v-1a7 7 0 0 0-7-7h-4a7 7 0 0 0-7 7v1h2v-1a5 5 0 0 1 5-5h4a5 5 0 0 1 5 5v1z"></path></svg>

After

Width:  |  Height:  |  Size: 253 B

21
frontend/src/components/CreatorCard.jsx

@ -0,0 +1,21 @@
export default function CreatorCard({ creator }) {
const handleClick = () => {
window.location.href = `/manage-channel/${creator.id}`;
};
return (
<div className="flex flex-col glassmorphism w-full p-6 cursor-pointer" onClick={handleClick}>
<img
src={creator.profilePicture}
alt={creator.name}
className="w-[128px] aspect-square object-cover rounded-full mx-auto"
/>
<h2 className="text-2xl font-medium font-inter mt-3 text-white">{creator.name}</h2>
<div className="text-sm text-gray-400 mt-1">
{creator.subscribers} abonné(e)s
</div>
</div>
);
}

38
frontend/src/components/Navbar.jsx

@ -1,12 +1,25 @@
import { useAuth } from '../contexts/AuthContext';
import React, { useState } from 'react';
import {useNavigate} from "react-router-dom";
export default function Navbar({ isSearchPage = false }) {
const { user, logout, isAuthenticated } = useAuth();
const [searchQuery, setSearchQuery] = useState('');
const navigate = useNavigate();
const handleLogout = () => {
logout();
};
const handleKeypress = (event) => {
if (event.key === 'Enter') {
const searchQuery = event.target.value;
if (searchQuery) {
navigate(`/search?q=${encodeURIComponent(searchQuery)}&type=videos`);
}
}
}
return (
<nav className="flex justify-between items-center p-4 text-white absolute top-0 left-0 w-screen">
<div>
@ -50,13 +63,24 @@ export default function Navbar({ isSearchPage = false }) {
</li>
</>
)}
<li className="bg-glass backdrop-blur-glass border-glass-full border rounded-sm text-white font-montserrat text-2xl font-black cursor-pointer w-[600/1920] h-[50px] px-3 flex items-center justify-center">
<input type="text" name="search" id="searchbar" placeholder="Rechercher" className="font-inter text-2xl font-normal focus:outline-none bg-transparent"/>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<circle cx="18.381" cy="13.619" r="12.119" stroke="white" strokeWidth="3"/>
<line x1="9.63207" y1="22.2035" x2="1.06064" y2="30.7749" stroke="white" strokeWidth="3"/>
</svg>
</li>
{ !isSearchPage && (
<li className="bg-glass backdrop-blur-glass border-glass-full border rounded-sm text-white font-montserrat text-2xl font-black cursor-pointer w-[600/1920] h-[50px] px-3 flex items-center justify-center">
<input
type="text"
name="search"
id="searchbar"
placeholder="Rechercher"
className="font-inter text-2xl font-normal focus:outline-none bg-transparent"
onKeyPress={(e) => handleKeypress(e)}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<circle cx="18.381" cy="13.619" r="12.119" stroke="white" strokeWidth="3"/>
<line x1="9.63207" y1="22.2035" x2="1.06064" y2="30.7749" stroke="white" strokeWidth="3"/>
</svg>
</li>
)}
</ul>
</div>
</nav>

7
frontend/src/index.css

@ -31,6 +31,13 @@
backdrop-filter: blur(27.5px);
}
.glassmorphism-rounded-full {
border-radius: 50%;
border: 2px solid rgba(239, 239, 239, 0.60);
background: linear-gradient(93deg, rgba(239, 239, 239, 0.06) 0%, rgba(239, 239, 239, 0.01) 100%);
backdrop-filter: blur(27.5px);
}
.resizable-none {
resize: none;
}

128
frontend/src/pages/Search.jsx

@ -0,0 +1,128 @@
import Navbar from "../components/Navbar.jsx";
import {useEffect, useState} from "react";
import {search} from "../services/search.service.js"
import VideoCard from "../components/VideoCard.jsx";
import CreatorCard from "../components/CreatorCard.jsx";
import {useSearchParams} from "react-router-dom";
export default function Search() {
const [searchParams, setSearchParams] = useSearchParams();
const queryFromUrl = searchParams.get('q') || '';
const typeFromUrl = searchParams.get('type') || 'videos';
const [filter, setFilter] = useState(typeFromUrl);
const [searchQuery, setSearchQuery] = useState(queryFromUrl);
const [results, setResults] = useState({});
useEffect(() => {
async function fetchData() {
if (searchParams) {
setResults(await search(queryFromUrl, typeFromUrl, 0, 20));
}
}
fetchData();
}, [searchParams]);
useEffect(() => {
async function fetchData() {
if (searchQuery) {
setResults(await search(searchQuery, filter, 0, 20));
}
}
fetchData();
}, [filter]);
const handleKeyPress = async (e) => {
if (e.key === 'Enter') {
setResults(await search(e.target.value, filter, 0, 20));
}
}
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={true} />
<main className="px-36 pt-[118px]">
{/* MEGA SEARCH BAR */}
<div className="flex items-center w-full gap-8" >
<div className="glassmorphism text-white font-montserrat text-2xl font-black cursor-pointer flex-1 h-[50px] px-3 flex items-center justify-center">
<input
type="text"
name="search"
id="searchbar"
placeholder="Rechercher"
className="font-inter text-2xl font-normal focus:outline-none bg-transparent w-full"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyPress={(e) => handleKeyPress(e) }
/>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="32" viewBox="0 0 32 32" fill="none" className="h-6 w-6">
<circle cx="18.381" cy="13.619" r="12.119" stroke="white" strokeWidth="3"/>
<line x1="9.63207" y1="22.2035" x2="1.06064" y2="30.7749" stroke="white" strokeWidth="3"/>
</svg>
</div>
{/* FILTERS */}
<form action="" className="flex w-max gap-4 items-center h-full">
<label
htmlFor="videos"
className={"p-3 " + (filter === "videos" ? "bg-white rounded-full fill-black border-2 border-white" : "glassmorphism-rounded-full") + " cursor-pointer"}
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className={(filter === "videos") ? "fill-black" : "fill-white"} ><path d="M7 6v12l10-6z"></path></svg>
</label>
<input
type="radio"
name="filter"
id="videos"
className="hidden"
onClick={() => setFilter("videos")}
/>
<label
htmlFor="channel"
className={"p-3 " + (filter === "channel" ? "bg-white rounded-full fill-black border-2 border-white" : "glassmorphism-rounded-full") + " cursor-pointer"}
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className={(filter === "channel") ? "fill-black" : "fill-white"} ><path d="M12 2a5 5 0 1 0 5 5 5 5 0 0 0-5-5zm0 8a3 3 0 1 1 3-3 3 3 0 0 1-3 3zm9 11v-1a7 7 0 0 0-7-7h-4a7 7 0 0 0-7 7v1h2v-1a5 5 0 0 1 5-5h4a5 5 0 0 1 5 5v1z"></path></svg>
</label>
<input
type="radio"
name="filter"
id="channel"
className="hidden"
onClick={() => setFilter("channel")}
/>
</form>
</div>
{/* RESULTS */}
<div className="grid grid-cols-4 gap-8 mt-8">
{results && results.length > 0 ? (
results.map((result, index) => {
if (result.type === "video") {
return (
<VideoCard key={index} video={result} />
);
} else if (result.type === "channel") {
return (
<CreatorCard key={index} creator={result} />
);
}
})
) : (
<div className="text-white text-center mt-10 col-span-4">
<h2 className="text-3xl font-bold">Aucun résultat trouvé</h2>
<p className="mt-4">Essayez de modifier votre recherche ou d'utiliser un autre filtre.</p>
</div>
)}
</div>
</main>
</div>
)
}

5
frontend/src/routes/routes.jsx

@ -7,6 +7,7 @@ import Account from "../pages/Account.jsx";
import ManageChannel from "../pages/ManageChannel.jsx";
import ManageVideo from "../pages/ManageVideo.jsx";
import AddVideo from "../pages/AddVideo.jsx";
import Search from "../pages/Search.jsx";
const routes = [
{ path: "/", element: <Home/> },
@ -61,6 +62,10 @@ const routes = [
<AddVideo/>
</ProtectedRoute>
)
},
{
path: "search",
element: <Search />
}
]

15
frontend/src/services/search.service.js

@ -0,0 +1,15 @@
export function search(query, filter, offset, limit) {
return fetch(`https://localhost/api/search?q=${encodeURIComponent(query)}&type=${filter}&offset=${offset}&limit=${limit}`)
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.catch((error) => {
console.error("There was a problem with the fetch operation:", error);
throw error;
});
}
Loading…
Cancel
Save