Browse Source

Merge pull request 'developpement' (#14) from developpement into main

Reviewed-on: #14
fix/clean
astria 3 months ago
parent
commit
d7cfbcf527
  1. 12
      backend/app/routes/user.route.js
  2. 2
      backend/app/utils/mail.js
  3. 90
      backend/logs/access.log
  4. 4
      docker-compose.yaml
  5. 4
      frontend/src/components/CreatorCard.jsx
  6. 459
      frontend/src/pages/Video.jsx

12
backend/app/routes/user.route.js

@ -32,25 +32,25 @@ import {Channel} from "../middlewares/channel.middleware.js";
const router = Router();
// REGISTER A USER
// REGISTER A USER+
router.post("/", [profileUpload.single("profile"), addLogger, UserRegister.email, UserRegister.username, UserRegister.password, validator, doEmailExists, doUsernameExists], register);
// LOGIN A USER
// LOGIN A USER+
router.post("/login", [addLogger, UserLogin.username, UserLogin.password, validator], login)
// SEARCH BY USERNAME
router.get("/search", [addLogger, isTokenValid, UserSearch.username, validator], searchByUsername);
// GET USER BY ID
// GET USER BY ID+
router.get("/:id", [addLogger, isTokenValid, User.id, validator], getById)
// GET USER BY USERNAME
// GET USER BY USERNAME+
router.get("/username/:username", [addLogger, isTokenValid, UserRequest.username, validator], getByUsername);
// UPDATE USER
// UPDATE USER+
router.put("/:id", [addLogger, isTokenValid, User.id, UserRegister.email, UserRegister.username, validator, doUserExists, isOwner], update);
// DELETE USER
// DELETE USER+
router.delete("/:id", [addLogger, isTokenValid, User.id, validator, doUserExists, isOwner], deleteUser);
// GET USER CHANNEL

2
backend/app/utils/mail.js

@ -7,7 +7,7 @@ function getTransporter() {
secure: false,
auth: {
user: process.env.GMAIL_USER,
pass: "yuuu kvoi ytrf blla",
pass: process.env.GMAIL_PASSWORD,
},
});
};

90
backend/logs/access.log

@ -12072,3 +12072,93 @@
[2025-09-05 17:57:44.224] [undefined] POST(/:id): user not the owner of the playlist with id 1 with status 403
[2025-09-05 17:58:14.088] [undefined] POST(/:id): Video added to playlist with id 2 with status 200
[2025-09-05 17:58:21.531] [undefined] GET(/:id): Playlist retrieved with id 2 with status 200
[2025-09-06 10:11:19.116] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:11:56.023] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:12:16.248] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:12:23.630] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-09-06 10:12:35.862] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:13:32.803] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:13:32.809] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:13:32.824] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
[2025-09-06 10:13:32.829] [undefined] GET(/:id/channel/subscribed): user 1 is not subscribed to channel 1 with status 200
[2025-09-06 10:13:58.268] [undefined] POST(/:id/subscribe): try to toggle subscription for channel with id 1
[2025-09-06 10:13:58.276] [undefined] POST(/:id/subscribe): Successfully subscribed to channel with status 200
[2025-09-06 10:14:41.764] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:14:41.775] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:14:41.789] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
[2025-09-06 10:14:41.794] [undefined] GET(/:id/channel/subscribed): user 1 is subscribed to channel 1 with status 200
[2025-09-06 10:14:42.428] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:14:42.437] [undefined] GET(/:id): try to get video 1
[2025-09-06 10:14:42.447] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-09-06 10:14:42.457] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-09-06 10:14:42.464] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-09-06 10:14:42.479] [undefined] GET(/:id/views): try to add views for video 1
[2025-09-06 10:14:42.484] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-09-06 10:14:49.450] [undefined] GET(/:id): try to get video 1
[2025-09-06 10:14:49.454] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:14:49.462] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-09-06 10:14:49.473] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-09-06 10:14:49.480] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-09-06 10:14:49.529] [undefined] GET(/:id/views): try to add views for video 1
[2025-09-06 10:14:49.536] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-09-06 10:14:51.048] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:14:51.054] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:14:51.065] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
[2025-09-06 10:14:51.071] [undefined] GET(/:id/channel/subscribed): user 1 is subscribed to channel 1 with status 200
[2025-09-06 10:15:00.735] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:15:00.741] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:15:00.754] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
[2025-09-06 10:15:00.759] [undefined] GET(/:id/channel/subscribed): user 1 is subscribed to channel 1 with status 200
[2025-09-06 10:15:01.676] [undefined] GET(/:id): try to get video 1
[2025-09-06 10:15:01.681] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:15:01.690] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-09-06 10:15:01.700] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-09-06 10:15:01.706] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-09-06 10:15:01.723] [undefined] GET(/:id/views): try to add views for video 1
[2025-09-06 10:15:01.731] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-09-06 10:15:54.639] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:15:54.651] [undefined] GET(/:id): try to get video 1
[2025-09-06 10:15:54.658] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-09-06 10:15:54.669] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-09-06 10:15:54.676] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-09-06 10:15:54.696] [undefined] GET(/:id/views): try to add views for video 1
[2025-09-06 10:15:54.704] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-09-06 10:15:57.359] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:15:57.365] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:15:57.377] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
[2025-09-06 10:15:57.382] [undefined] GET(/:id/channel/subscribed): user 1 is subscribed to channel 1 with status 200
[2025-09-06 10:16:01.637] [undefined] GET(/:id): try to get video 1
[2025-09-06 10:16:01.642] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:16:01.649] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-09-06 10:16:01.658] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-09-06 10:16:01.666] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-09-06 10:16:01.710] [undefined] GET(/:id/views): try to add views for video 1
[2025-09-06 10:16:01.716] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-09-06 10:16:04.193] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:16:13.098] [undefined] GET(/:id): try to get channel with id 2
[2025-09-06 10:16:13.154] [undefined] GET(/:id): Successfully get channel with id 2 with status 200
[2025-09-06 10:16:13.165] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 2
[2025-09-06 10:16:13.170] [undefined] GET(/:id/channel/subscribed): user 1 is not subscribed to channel 2 with status 200
[2025-09-06 10:19:06.785] [undefined] GET(/:id): try to get channel with id 2
[2025-09-06 10:19:06.798] [undefined] GET(/:id): Successfully get channel with id 2 with status 200
[2025-09-06 10:19:06.814] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 2
[2025-09-06 10:19:06.819] [undefined] GET(/:id/channel/subscribed): user 1 is not subscribed to channel 2 with status 200
[2025-09-06 10:19:15.009] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:23:43.105] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-09-06 10:23:43.111] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-09-06 10:23:43.117] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-09-06 10:23:43.124] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
[2025-09-06 10:23:43.129] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:23:44.349] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:23:44.354] [undefined] GET(/:id/stats): try to get stats
[2025-09-06 10:23:44.359] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-09-06 10:23:44.365] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:23:47.007] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-09-06 10:23:47.013] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-09-06 10:23:47.066] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
[2025-09-06 10:23:47.070] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-09-06 10:23:47.077] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:23:54.346] [undefined] GET(/:id): try to get channel with id 2
[2025-09-06 10:23:54.352] [undefined] GET(/:id): Successfully get channel with id 2 with status 200
[2025-09-06 10:23:54.364] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 2
[2025-09-06 10:23:54.370] [undefined] GET(/:id/channel/subscribed): user 1 is not subscribed to channel 2 with status 200

4
docker-compose.yaml

@ -23,7 +23,7 @@ services:
FRONTEND_URL: ${FRONTEND_URL}
volumes:
- ./backend/logs:/var/log/freetube
- ./backend:/app
- upload:/app/backend/app/uploads
depends_on:
db:
condition: service_healthy
@ -60,4 +60,6 @@ services:
volumes:
db_data:
driver: local
upload:
driver: local

4
frontend/src/components/CreatorCard.jsx

@ -2,9 +2,11 @@
export default function CreatorCard({ creator }) {
const handleClick = () => {
window.location.href = `/manage-channel/${creator.id}`;
window.location.href = `/channel/${creator.channel_id}`;
};
console.log(creator);
return (
<div className="flex flex-col glassmorphism w-full p-6 cursor-pointer" onClick={handleClick}>
<img

459
frontend/src/pages/Video.jsx

@ -1,20 +1,20 @@
import {useNavigate, useParams, useSearchParams} from "react-router-dom";
import {useEffect, useState, useRef, useCallback} from "react";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { useEffect, useState, useRef, useCallback } from "react";
import Navbar from "../components/Navbar.jsx";
import { useAuth } from "../contexts/AuthContext.jsx";
import Comment from "../components/Comment.jsx";
import VideoCard from "../components/VideoCard.jsx";
import Tag from "../components/Tag.jsx";
import {addView, getSimilarVideos, getVideoById, toggleLike} from "../services/video.service.js";
import {subscribe} from "../services/channel.service.js";
import {addComment} from "../services/comment.service.js";
import { addView, getSimilarVideos, getVideoById, toggleLike } from "../services/video.service.js";
import { subscribe } from "../services/channel.service.js";
import { addComment } from "../services/comment.service.js";
import { getPlaylists } from "../services/user.service.js";
import { addToPlaylist, getPlaylistById } from "../services/playlist.service.js";
import PlaylistVideoCard from "../components/PlaylistVideoCard.jsx";
export default function Video() {
const {id} = useParams();
const { id } = useParams();
const { user, isAuthenticated } = useAuth();
const videoRef = useRef(null);
const controllerRef = useRef(null);
@ -95,7 +95,7 @@ export default function Video() {
return video.id.toString() === id.toString();
});
console.log("currentIndex", currentIndex);
if (currentIndex !== -1) {
if (currentPlaylist?.videos[currentIndex + 1]) {
setNextVideo(currentPlaylist.videos[currentIndex + 1]);
@ -110,7 +110,7 @@ export default function Video() {
return;
}
console.log("Passing to next video:", nextVideo);
// Navigate to the next video with playlist context
if (playlistId) {
navigation(`/video/${nextVideo.id}?playlistId=${playlistId}`);
@ -228,12 +228,12 @@ export default function Video() {
const handleMouseMove = () => {
if (isFullscreen) {
setShowControls(true);
// Clear existing timeout
if (hideControlsTimeoutRef.current) {
clearTimeout(hideControlsTimeoutRef.current);
}
// Hide controls after 3 seconds of no mouse movement
hideControlsTimeoutRef.current = setTimeout(() => {
setShowControls(false);
@ -339,101 +339,100 @@ export default function Video() {
setIsAddToPlaylistOpen(!isAddToPlaylistOpen);
}
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-5 lg:px-36 w-full lg:flex justify-between pt-[48px] lg:pt-[118px]">
{video ? (
<>
{/* Video player section */}
<div className="lg:w-1280/1920">
<div
className="relative w-full aspect-video mx-auto rounded-lg overflow-hidden"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseMove={handleMouseMove}
>
<video
key={video.id}
id={`video-${video.id}`}
ref={videoRef}
onPlay={handlePlaying}
onTimeUpdate={handleTimeUpdate}
onLoadedMetadata={handleLoadedMetadata}
onEnded={passToNextVideo}
className={`w-full h-full object-cover cursor-pointer ${isFullscreen ? 'fixed top-0 left-0 w-full h-full z-50 bg-black' : ''}`}
controls={window.innerWidth < 1024} // Show native controls on small screens
>
<source src={`${video.file}`} type="video/mp4" />
Your browser does not support the video tag.
</video>
{/* Video controls */}
<div
className={`glassmorphism-rounded-md p-4 hidden lg:flex gap-4 items-center transition-opacity duration-300 ${
showControls ? 'opacity-100' : 'opacity-0'
}
${isFullscreen ? 'fixed bottom-8 left-1/2 transform -translate-x-1/2 opacity-100 z-[60] w-[70%] max-w-3xl' : 'absolute bottom-4 left-4 right-4'}`}
ref={controllerRef}
>
<p
className="text-white"
onClick={() => {
if (videoRef.current) {
videoRef.current.currentTime = Math.max(0, videoRef.current.currentTime - 10);
}
}}
>-10</p>
<svg width="30" height="34" viewBox="0 0 30 34" fill="none" xmlns="http://www.w3.org/2000/svg" onClick={handlePlayPause}>
<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>
<p
className="text-white"
onClick={() => {
if (videoRef.current) {
videoRef.current.currentTime = Math.min(videoRef.current.currentTime + 10, duration);
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-5 lg:px-36 w-full lg:flex justify-between pt-[48px] lg:pt-[118px]">
{video ? (
<>
{/* Video player section */}
<div className="lg:w-1280/1920">
<div
className="relative w-full aspect-video mx-auto rounded-lg overflow-hidden"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseMove={handleMouseMove}
>
<video
key={video.id}
id={`video-${video.id}`}
ref={videoRef}
onPlay={handlePlaying}
onTimeUpdate={handleTimeUpdate}
onLoadedMetadata={handleLoadedMetadata}
onEnded={passToNextVideo}
className={`w-full h-full object-cover cursor-pointer ${isFullscreen ? 'fixed top-0 left-0 w-full h-full z-50 bg-black' : ''}`}
controls={window.innerWidth < 1024} // Show native controls on small screens
>
<source src={`${video.file}`} type="video/mp4" />
Your browser does not support the video tag.
</video>
{/* Video controls */}
<div
className={`glassmorphism-rounded-md p-4 hidden lg:flex gap-4 items-center transition-opacity duration-300 ${showControls ? 'opacity-100' : 'opacity-0'
}
}}
>+10</p>
<div className="flex-1 mx-4">
{/* Time display */}
<div className="flex justify-between items-center text-white text-sm mt-2">
<span>{formatTime(currentTime)}</span>
<span>{formatTime(duration)}</span>
</div>
{/* Time bar */}
<div
className="w-full h-2 bg-gray-300 rounded-full mt-2 cursor-pointer hover:h-3 transition-all duration-200"
onClick={handleTimeBarClick}
>
<div
className="h-full bg-white rounded-full transition-all duration-100"
style={{ width: `${progress}%` }}
></div>
</div>
</div>
${isFullscreen ? 'fixed bottom-8 left-1/2 transform -translate-x-1/2 opacity-100 z-[60] w-[70%] max-w-3xl' : 'absolute bottom-4 left-4 right-4'}`}
ref={controllerRef}
>
<p
className="text-white"
onClick={() => {
if (videoRef.current) {
videoRef.current.currentTime = Math.max(0, videoRef.current.currentTime - 10);
}
}}
>-10</p>
<svg width="30" height="34" viewBox="0 0 30 34" fill="none" xmlns="http://www.w3.org/2000/svg" onClick={handlePlayPause}>
<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>
<p
className="text-white"
onClick={() => {
if (videoRef.current) {
videoRef.current.currentTime = Math.min(videoRef.current.currentTime + 10, duration);
}
}}
>+10</p>
<div className="flex-1 mx-4">
{/* Time display */}
<div className="flex justify-between items-center text-white text-sm mt-2">
<span>{formatTime(currentTime)}</span>
<span>{formatTime(duration)}</span>
</div>
{/* Time bar */}
<div
className="w-full h-2 bg-gray-300 rounded-full mt-2 cursor-pointer hover:h-3 transition-all duration-200"
onClick={handleTimeBarClick}
>
<div
className="h-full bg-white rounded-full transition-all duration-100"
style={{ width: `${progress}%` }}
></div>
</div>
</div>
{
!isFullscreen ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
className="fill-white"
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
className="fill-white"
viewBox="0 0 24 24"
onClick={() => setIsFullscreen(true)}
>
<path d="M5 5h5V3H3v7h2zm5 14H5v-5H3v7h7zm11-5h-2v5h-5v2h7zm-2-4h2V3h-7v2h5z"></path>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className="fill-white"
onClick={() => setIsFullscreen(false)}
@ -443,144 +442,152 @@ export default function Video() {
)
}
</div>
</div>
<h1 className="mt-3 font-montserrat font-bold text-2xl text-white">{video.title}</h1>
{/* Channel and like */}
<div className="lg:flex items-center mt-4">
<div className="flex">
<img
src={video.creator?.profile_picture || "https://placehold.co/48"}
alt={video.creator?.name || "Creator"}
className="w-12 h-12 rounded-full object-cover mr-3"
/>
<div>
<p className="text-white font-bold font-montserrat">{video.creator?.name}</p>
<p className="text-gray-300 text-sm">{video.creator?.subscribers || 0} abonnés</p>
</div>
<button className="ml-14 bg-primary text-white font-montserrat font-bold px-4 py-2 rounded-md cursor-pointer" onClick={handleSubscribe} >
s'abonner
</button>
</div>
</div>
<h1 className="mt-3 font-montserrat font-bold text-2xl text-white">{video.title}</h1>
{/* Channel and like */}
<div className="lg:flex items-center mt-4">
<div className="flex">
<img
src={video.creator?.profile_picture || "https://placehold.co/48"}
alt={video.creator?.name || "Creator"}
className="w-12 h-12 rounded-full object-cover mr-3 cursor-pointer"
onClick={() => {
window.location.href = `/channel/${video.creator?.id}`;
}}
/>
<div>
<p
className="text-white font-bold font-montserrat cursor-pointer"
onClick={() => {
window.location.href = `/channel/${video.creator?.id}`;
}}
>{video.creator?.name}</p>
<p className="text-gray-300 text-sm">{video.creator?.subscribers || 0} abonnés</p>
</div>
<button className="ml-14 bg-primary text-white font-montserrat font-bold px-4 py-2 rounded-md cursor-pointer" onClick={handleSubscribe} >
s'abonner
</button>
</div>
<div className="flex items-center lg:ml-auto mt-4 lg:mt-0">
<button className="lg:ml-4 cursor-pointer" onClick={handleLike}>
<svg width="32" height="32" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 31.5H7.5V12H6C5.20435 12 4.44129 12.3161 3.87868 12.8787C3.31607 13.4413 3 14.2044 3 15V28.5C3 29.2956 3.31607 30.0587 3.87868 30.6213C4.44129 31.1839 5.20435 31.5 6 31.5ZM30 12H19.5L21.183 6.948C21.3332 6.49712 21.3741 6.01702 21.3024 5.54723C21.2306 5.07745 21.0483 4.63142 20.7705 4.24589C20.4926 3.86036 20.1271 3.54636 19.7041 3.32975C19.2811 3.11314 18.8127 3.00012 18.3375 3H18L10.5 11.157V31.5H27L32.868 18.606L33 18V15C33 14.2044 32.6839 13.4413 32.1213 12.8787C31.5587 12.3161 30.7956 12 30 12Z" fill="white" />
</svg>
</button>
<p className="font-montserrat text-white ml-2" >{video.likes}</p>
{
isAuthenticated && (
<button className="relative ml-14">
<div className="bg-primary cursor-pointer px-4 py-2 rounded-md flex items-center gap-4" onClick={() => { setIsAddToPlaylistOpen(!isAddToPlaylistOpen) }} >
<p className="text-white font-montserrat font-bold" >playlist</p>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="fill-white"><path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"></path></svg>
</div>
<div className="flex items-center lg:ml-auto mt-4 lg:mt-0">
<button className="lg:ml-4 cursor-pointer" onClick={handleLike}>
<svg width="32" height="32" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 31.5H7.5V12H6C5.20435 12 4.44129 12.3161 3.87868 12.8787C3.31607 13.4413 3 14.2044 3 15V28.5C3 29.2956 3.31607 30.0587 3.87868 30.6213C4.44129 31.1839 5.20435 31.5 6 31.5ZM30 12H19.5L21.183 6.948C21.3332 6.49712 21.3741 6.01702 21.3024 5.54723C21.2306 5.07745 21.0483 4.63142 20.7705 4.24589C20.4926 3.86036 20.1271 3.54636 19.7041 3.32975C19.2811 3.11314 18.8127 3.00012 18.3375 3H18L10.5 11.157V31.5H27L32.868 18.606L33 18V15C33 14.2044 32.6839 13.4413 32.1213 12.8787C31.5587 12.3161 30.7956 12 30 12Z" fill="white"/>
</svg>
</button>
<p className="font-montserrat text-white ml-2" >{video.likes}</p>
{
isAuthenticated && (
<button className="relative ml-14">
<div className="bg-primary cursor-pointer px-4 py-2 rounded-md flex items-center gap-4" onClick={() => { setIsAddToPlaylistOpen(!isAddToPlaylistOpen) }} >
<p className="text-white font-montserrat font-bold" >playlist</p>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="fill-white"><path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"></path></svg>
</div>
{
playlists.length > 0 && isAddToPlaylistOpen && (
<div className="absolute inset-0 w-max h-max z-40 glassmorphism top-1/1 mt-2 left-0 rounded-2xl px-4 py-2 cursor-default">
<ul className="flex flex-col gap-2">
{playlists.map((playlist) => (
<li
key={playlist.id}
className="text-white font-montserrat font-medium text-sm cursor-pointer hover:underline flex items-center justify-between gap-4"
onClick={() => handleAddToPlaylist(playlist.id)}
>
<p className="text-start">{playlist.name}</p>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="fill-white"><path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"></path></svg>
</li>
))}
</ul>
</div>
<div className="absolute inset-0 w-max h-max z-40 glassmorphism top-1/1 mt-2 left-0 rounded-2xl px-4 py-2 cursor-default">
<ul className="flex flex-col gap-2">
{playlists.map((playlist) => (
<li
key={playlist.id}
className="text-white font-montserrat font-medium text-sm cursor-pointer hover:underline flex items-center justify-between gap-4"
onClick={() => handleAddToPlaylist(playlist.id)}
>
<p className="text-start">{playlist.name}</p>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="fill-white"><path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"></path></svg>
</li>
))}
</ul>
</div>
)
}
</button>
</button>
)
}
</div>
</div>
{/* Video details */}
<div className="glassmorphism rounded-md py-4 px-6 mt-7">
{/* Tags */}
{video.tags && video.tags.length > 0 && (
<div className="mb-3">
<div className="flex flex-wrap gap-2">
{video.tags.map((tag, index) => (
<Tag tag={tag} key={index} doShowControls={false} />
))}
</div>
</div>
)}
<p className="font-montserrat text-white">{video.views} vues - {formatDate(video.release_date)}</p>
<p className="font-montserrat text-white mt-2">{video.description}</p>
</div>
{/* Comments section */}
<div>
<div className="flex justify-between items-center mt-8 mb-2">
<h2 className="font-montserrat text-white text-2xl">Commentaires</h2>
<button className="text-white lg:hidden" onClick={() => setIsCommentVisible(!isCommentVisible)} >
Voir {isCommentVisible ? "moins" : "plus"}
</button>
</div>
<textarea
className="glassmorphism h-[100px] w-full font-inter text-white placeholder:text-[#9f9f9f] focus:outline-none py-4 px-6"
placeholder="Ajouter un commentaire..."
value={comment}
onChange={(e) => setComment(e.target.value)}
></textarea>
<button className="bg-primary text-white font-montserrat font-bold px-4 py-2 rounded-md mt-2 cursor-pointer" onClick={handleComment}>
Publier
</button>
{/* Comments list */}
{
isCommentVisible && (
<div className="mt-4">
{video.comments && video.comments.length > 0 ? (
video.comments.map((comment, index) => (
<Comment comment={comment} index={index} videoId={id} key={index} refetchVideo={fetchVideo} addAlert={addAlert} />
))
) : (
<p className="text-gray-400">Aucun commentaire pour le moment. Soyez le premier à en publier !</p>
)}
</div>
)
}
</div>
</div>
{/* Video details */}
<div className="glassmorphism rounded-md py-4 px-6 mt-7">
{/* Tags */}
{video.tags && video.tags.length > 0 && (
<div className="mb-3">
<div className="flex flex-wrap gap-2">
{video.tags.map((tag, index) => (
<Tag tag={tag} key={index} doShowControls={false} />
))}
</div>
</div>
)}
<p className="font-montserrat text-white">{video.views} vues - {formatDate(video.release_date)}</p>
<p className="font-montserrat text-white mt-2">{video.description}</p>
</div>
{/* Comments section */}
<div>
<div className="flex justify-between items-center mt-8 mb-2">
<h2 className="font-montserrat text-white text-2xl">Commentaires</h2>
<button className="text-white lg:hidden" onClick={() => setIsCommentVisible(!isCommentVisible)} >
Voir {isCommentVisible ? "moins" : "plus"}
</button>
</div>
<textarea
className="glassmorphism h-[100px] w-full font-inter text-white placeholder:text-[#9f9f9f] focus:outline-none py-4 px-6"
placeholder="Ajouter un commentaire..."
value={comment}
onChange={(e) => setComment(e.target.value)}
></textarea>
<button className="bg-primary text-white font-montserrat font-bold px-4 py-2 rounded-md mt-2 cursor-pointer" onClick={handleComment}>
Publier
</button>
{/* Comments list */}
{
isCommentVisible && (
<div className="mt-4">
{video.comments && video.comments.length > 0 ? (
video.comments.map((comment, index) => (
<Comment comment={comment} index={index} videoId={id} key={index} refetchVideo={fetchVideo} addAlert={addAlert} />
))
) : (
<p className="text-gray-400">Aucun commentaire pour le moment. Soyez le premier à en publier !</p>
)}
</div>
)
}
</div>
</div>
</div>
</div>
{/* Similar videos section */}
<div className="flex-1">
<div className="flex-1">
{
!isPlaylist ? (
<div className="flex flex-col items-center gap-2">
<h2 className="font-montserrat w-full lg:w-9/10 mt-8 text-white text-2xl">Recommandations</h2>
{similarVideos.length > 0 ? similarVideos.map((video, index) => (
<div className="w-full lg:w-9/10" key={index}>
<VideoCard video={video} />
</div>
)) : (
<p className="text-gray-500 text-left">Aucune recommandation disponible</p>
)}
</div>
<div className="flex flex-col items-center gap-2">
<h2 className="font-montserrat w-full lg:w-9/10 mt-8 text-white text-2xl">Recommandations</h2>
{similarVideos.length > 0 ? similarVideos.map((video, index) => (
<div className="w-full lg:w-9/10" key={index}>
<VideoCard video={video} />
</div>
)) : (
<p className="text-gray-500 text-left">Aucune recommandation disponible</p>
)}
</div>
) : (
<div className="flex flex-col items-center gap-2">
<div className="glassmorphism w-full lg:w-9/10 py-4 px-2" >
<div className="flex flex-col items-center gap-2">
<div className="glassmorphism w-full lg:w-9/10 py-4 px-2" >
<h2 className="font-montserrat text-white text-2xl">{currentPlaylist?.name}</h2>
{
currentPlaylist?.videos && currentPlaylist.videos.length > 0 ? (
@ -595,18 +602,18 @@ export default function Video() {
<p className="font-montserrat text-white mt-2">Aucune vidéo trouvée dans cette playlist.</p>
)
}
</div>
</div>
</div>
</div>
)
}
</div>
</>
): (
<p>Loading</p>
)}
</div>
</>
) : (
<p>Loading</p>
)}
</main>
</main>
</div>
);
</div>
);
}
Loading…
Cancel
Save