Browse Source

Merge pull request 'features/responsive' (#8) from features/responsive into developpement

Reviewed-on: #8
pull/10/head
astria 4 months ago
parent
commit
23cf3298f9
  1. 1177
      backend/logs/access.log
  2. 2
      frontend/src/components/ChannelLastVideos.jsx
  3. 4
      frontend/src/components/Recommendations.jsx
  4. 2
      frontend/src/components/SeeLater.jsx
  5. 6
      frontend/src/components/TopCreators.jsx
  6. 2
      frontend/src/components/TrendingVideos.jsx
  7. 2
      frontend/src/components/VideoStatListElement.jsx
  8. 23
      frontend/src/contexts/AuthContext.jsx
  9. 4
      frontend/src/modals/CreateChannelModal.jsx
  10. 4
      frontend/src/modals/CreatePlaylistModal.jsx
  11. 4
      frontend/src/modals/EmailVerificationModal.jsx
  12. 6
      frontend/src/modals/VerificationModal.jsx
  13. 30
      frontend/src/pages/Account.jsx
  14. 4
      frontend/src/pages/AddVideo.jsx
  15. 48
      frontend/src/pages/Channel.jsx
  16. 10
      frontend/src/pages/Home.jsx
  17. 6
      frontend/src/pages/ManageChannel.jsx
  18. 12
      frontend/src/pages/ManageVideo.jsx
  19. 4
      frontend/src/pages/Playlist.jsx
  20. 4
      frontend/src/pages/Search.jsx
  21. 134
      frontend/src/pages/Video.jsx

1177
backend/logs/access.log

File diff suppressed because it is too large

2
frontend/src/components/ChannelLastVideos.jsx

@ -3,7 +3,7 @@ import VideoCard from "./VideoCard.jsx";
export default function ChannelLastVideos({ videos }) { export default function ChannelLastVideos({ videos }) {
return ( return (
<div className="grid grid-cols-4 gap-8"> <div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
{ {
videos && videos.length > 0 ? ( videos && videos.length > 0 ? (
videos.map((video) => ( videos.map((video) => (

4
frontend/src/components/Recommendations.jsx

@ -4,9 +4,9 @@ export default function Recommendations({videos}) {
console.log(videos); console.log(videos);
return ( return (
<div className=""> <div className="">
<h2 className="text-3xl font-bold mb-4 text-white">Recommendations</h2> <h2 className="text-3xl font-bold mb-4 text-white">Recommandations</h2>
<div> <div>
<div className="grid grid-cols-5 gap-8 mt-2"> <div className="grid grid-cols-1 lg:grid-cols-5 gap-8 mt-2">
{videos && videos.map((video, index) => ( {videos && videos.map((video, index) => (
<VideoCard key={video.id || index} video={video} /> <VideoCard key={video.id || index} video={video} />
))} ))}

2
frontend/src/components/SeeLater.jsx

@ -7,7 +7,7 @@ export default function SeeLater({videos}) {
<div className="mt-10"> <div className="mt-10">
<h2 className="text-3xl font-bold mb-4 text-white">A regarder plus tard</h2> <h2 className="text-3xl font-bold mb-4 text-white">A regarder plus tard</h2>
<div> <div>
<div className="grid grid-cols-5 gap-8 mt-2"> <div className="grid grid-cols-1 lg:grid-cols-5 gap-8 mt-2">
{videos && videos.map((video, index) => ( {videos && videos.map((video, index) => (
<VideoCard key={video.id || index} video={video} /> <VideoCard key={video.id || index} video={video} />
))} ))}

6
frontend/src/components/TopCreators.jsx

@ -4,14 +4,14 @@ export default function TopCreators({ creators, navigate }) {
return ( return (
<div className="mt-10"> <div className="mt-10">
<h2 className="text-3xl font-bold mb-4 text-white">Top Créateurs</h2> <h2 className="text-3xl font-bold mb-4 text-white">Top Créateurs</h2>
<div className="grid grid-cols-5 gap-8 mt-8"> <div className="grid grid-cols-1 lg:grid-cols-5 gap-8 mt-8">
{creators && creators.map((creator, index) => ( {creators && creators.map((creator, index) => (
<div <div
key={creator.id || index} key={creator.id || index}
className="flex flex-col items-center glassmorphism " className="flex flex-col items-center glassmorphism py-2"
onClick={() => navigate(`/channel/${creator.id}`)} onClick={() => navigate(`/channel/${creator.id}`)}
> >
<img src={creator.profilepicture} alt={creator.name} className="w-[128px] h-auto rounded-full" /> <img src={creator.profilepicture} alt={creator.name} className="w-[128px] aspect-square rounded-full" />
<h3 className="text-xl text-white font-bold mt-1">{creator.name}</h3> <h3 className="text-xl text-white font-bold mt-1">{creator.name}</h3>
<span className="text-sm text-gray-500">{creator.subscriber_count} abonné{creator.subscriber_count > 1 ? 's' : ''}</span> <span className="text-sm text-gray-500">{creator.subscriber_count} abonné{creator.subscriber_count > 1 ? 's' : ''}</span>
<p className="text-center text-gray-400"> <p className="text-center text-gray-400">

2
frontend/src/components/TrendingVideos.jsx

@ -6,7 +6,7 @@ export default function TrendingVideos({ videos }) {
return ( return (
<div className="mt-10"> <div className="mt-10">
<h2 className="text-3xl font-bold mb-4 text-white">Tendances</h2> <h2 className="text-3xl font-bold mb-4 text-white">Tendances</h2>
<div className="grid grid-cols-5 gap-8 mt-2"> <div className="grid grid-cols-1 lg:grid-cols-5 gap-8 mt-2">
{videos && videos.map((video, index) => ( {videos && videos.map((video, index) => (
<VideoCard video={video} key={index} /> <VideoCard video={video} key={index} />
))} ))}

2
frontend/src/components/VideoStatListElement.jsx

@ -9,7 +9,7 @@ export default function VideoStatListElement ({ video, onClick }) {
className="w-1/4 aspect-video rounded-sm" className="w-1/4 aspect-video rounded-sm"
/> />
<div> <div>
<h3 className="text-white text-2xl font-montserrat font-bold" >{video.title}</h3> <h3 className="text-white text-lg lg:text-2xl font-montserrat font-bold" >{video.title.slice(0, 25)}{video.title.length > 25 ? "...":""}</h3>
<p className="text-white text-lg font-montserrat font-normal">Vues: {video.views}</p> <p className="text-white text-lg font-montserrat font-normal">Vues: {video.views}</p>
<p className="text-white text-lg font-montserrat font-normal">Likes: {video.likes}</p> <p className="text-white text-lg font-montserrat font-normal">Likes: {video.likes}</p>
<p className="text-white text-lg font-montserrat font-normal">Commentaires: {video.comments}</p> <p className="text-white text-lg font-montserrat font-normal">Commentaires: {video.comments}</p>

23
frontend/src/contexts/AuthContext.jsx

@ -1,4 +1,4 @@
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; import React, { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext(); const AuthContext = createContext();
@ -25,7 +25,7 @@ export const AuthProvider = ({ children }) => {
setLoading(false); setLoading(false);
}, []); }, []);
const login = useCallback(async (username, password) => { const login = async (username, password) => {
try { try {
const response = await fetch('/api/users/login', { const response = await fetch('/api/users/login', {
method: 'POST', method: 'POST',
@ -50,9 +50,9 @@ export const AuthProvider = ({ children }) => {
} catch (error) { } catch (error) {
throw error; throw error;
} }
}, []); };
const register = useCallback(async (email, username, password, profileImage) => { const register = async (email, username, password, profileImage) => {
try { try {
const formData = new FormData(); const formData = new FormData();
formData.append('email', email); formData.append('email', email);
@ -81,26 +81,25 @@ export const AuthProvider = ({ children }) => {
} catch (error) { } catch (error) {
throw error; throw error;
} }
}, []); };
const loginWithOAuth = useCallback((userData, token) => { const loginWithOAuth = (userData, token) => {
console.log('OAuth login called with:', userData);
// Store token and user data // Store token and user data
localStorage.setItem('token', token); localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(userData)); localStorage.setItem('user', JSON.stringify(userData));
setUser(userData); setUser(userData);
}, []); };
const logout = useCallback(() => { const logout = () => {
localStorage.removeItem('token'); localStorage.removeItem('token');
localStorage.removeItem('user'); localStorage.removeItem('user');
setUser(null); setUser(null);
}, []); };
const getAuthHeaders = useCallback(() => { const getAuthHeaders = () => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
return token ? { Authorization: `Bearer ${token}` } : {}; return token ? { Authorization: `Bearer ${token}` } : {};
}, []); };
const value = { const value = {
user, user,

4
frontend/src/modals/CreateChannelModal.jsx

@ -28,8 +28,8 @@ export default function CreateChannelModal({isOpen, onClose, addAlert}) {
} }
return isOpen && ( return isOpen && (
<div className="bg-[#00000080] fixed top-0 left-0 w-screen h-screen flex flex-col items-center justify-center" > <div className="bg-[#00000080] fixed top-0 left-0 w-screen h-screen flex flex-col items-center justify-center px-5 lg:px-0" >
<div className="glassmorphism p-4 w-1/4" > <div className="glassmorphism p-4 w-full lg:w-1/4" >
<h2 className="text-2xl text-white font-montserrat font-bold" >Créer une chaine</h2> <h2 className="text-2xl text-white font-montserrat font-bold" >Créer une chaine</h2>
<label htmlFor="name" className="block text-xl text-white font-montserrat font-semibold mt-2" >Nom de la chaine</label> <label htmlFor="name" className="block text-xl text-white font-montserrat font-semibold mt-2" >Nom de la chaine</label>
<input <input

4
frontend/src/modals/CreatePlaylistModal.jsx

@ -15,8 +15,8 @@ export default function CreatePlaylistModal({ isOpen, onClose, addAlert }) {
return isOpen && ( return isOpen && (
<div className="bg-[#00000080] fixed top-0 left-0 w-screen h-screen flex flex-col items-center justify-center" > <div className="bg-[#00000080] fixed top-0 left-0 w-screen h-screen flex flex-col items-center justify-center px-5 lg:px-0" >
<div className="glassmorphism p-4 w-1/4" > <div className="glassmorphism p-4 w-full lg:w-1/4" >
<h2 className="text-2xl text-white font-montserrat font-bold" >Créer une playlist</h2> <h2 className="text-2xl text-white font-montserrat font-bold" >Créer une playlist</h2>
<label htmlFor="name" className="block text-xl text-white font-montserrat font-semibold mt-2" >Nom de la playlist</label> <label htmlFor="name" className="block text-xl text-white font-montserrat font-semibold mt-2" >Nom de la playlist</label>
<input <input

4
frontend/src/modals/EmailVerificationModal.jsx

@ -5,8 +5,8 @@ export default function EmailVerificationModal({ isOpen, onSubmit, onClose }) {
const [verificationCode, setVerificationCode] = useState(''); const [verificationCode, setVerificationCode] = useState('');
return isOpen && ( return isOpen && (
<div className="bg-[#00000080] fixed top-0 left-0 w-screen h-screen flex flex-col items-center justify-center" > <div className="bg-[#00000080] fixed top-0 left-0 w-screen h-screen flex flex-col items-center justify-center px-5 lg:px-0" >
<div className="glassmorphism p-4 w-1/4" > <div className="glassmorphism p-4 w-full lg:w-1/4" >
<h2 className="text-lg font-bold mb-2 font-montserrat text-white">Vérification de l'email</h2> <h2 className="text-lg font-bold mb-2 font-montserrat text-white">Vérification de l'email</h2>
<p className="font-montserrat text-white">Un email de vérification a été envoyé à votre adresse email. Veuillez vérifier votre boîte de réception.</p> <p className="font-montserrat text-white">Un email de vérification a été envoyé à votre adresse email. Veuillez vérifier votre boîte de réception.</p>
<input <input

6
frontend/src/modals/VerificationModal.jsx

@ -4,10 +4,10 @@ export default function VerificationModal({title, onConfirm, onCancel, isOpen})
if (!isOpen) return null; if (!isOpen) return null;
return ( return (
<div className="fixed inset-0 flex items-center justify-center"> <div className="fixed inset-0 flex items-center justify-center px-5 lg:px-0">
<div className="glassmorphism p-6"> <div className="glassmorphism w-full lg:w-auto p-6">
<h2 className="text-lg text-white font-semibold mb-4">{title}</h2> <h2 className="text-lg text-white font-semibold mb-4">{title}</h2>
<div className="flex justify-end"> <div className="flex justify-start lg:justify-end">
<button <button
className="bg-primary px-3 py-2 rounded-sm text-white font-montserrat text-lg font-semibold cursor-pointer" className="bg-primary px-3 py-2 rounded-sm text-white font-montserrat text-lg font-semibold cursor-pointer"
onClick={() => onConfirm()} onClick={() => onConfirm()}

30
frontend/src/pages/Account.jsx

@ -46,7 +46,7 @@ export default function Account() {
const [editMode, setEditMode] = useState(false); const [editMode, setEditMode] = useState(false);
const nonEditModeClasses = "text-2xl font-bold text-white p-2 focus:text-white focus:outline-none w-full font-montserrat"; const nonEditModeClasses = "text-lg lg:text-2xl font-bold text-white p-2 focus:text-white focus:outline-none w-full font-montserrat";
const editModeClasses = nonEditModeClasses + " glassmorphism"; const editModeClasses = nonEditModeClasses + " glassmorphism";
const handlePlaylistClick = (playlistId) => { const handlePlaylistClick = (playlistId) => {
@ -95,11 +95,11 @@ export default function Account() {
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <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} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-36 pt-[118px] flex justify-between items-start"> <main className="px-5 lg:px-36 pt-[48px] lg:pt-[118px] lg:flex justify-between items-start">
{/* Left side */} {/* Left side */}
{/* Profile / Edit profile */} {/* Profile / Edit profile */}
<form className="glassmorphism w-1/3 p-10"> <form className="glassmorphism lg:w-1/3 p-10">
<div className="relative w-1/3 aspect-square overflow-hidden mb-3 mx-auto" onMouseEnter={() => setIsPictureEditActive(true)} onMouseLeave={() => setIsPictureEditActive(false)} > <div className="relative w-1/3 aspect-square overflow-hidden mb-3 mx-auto" onMouseEnter={() => setIsPictureEditActive(true)} onMouseLeave={() => setIsPictureEditActive(false)} >
<label htmlFor="image"> <label htmlFor="image">
<img <img
@ -114,7 +114,7 @@ export default function Account() {
</label> </label>
<input type="file" accept="image/*" id="image" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" disabled={!editMode} /> <input type="file" accept="image/*" id="image" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" disabled={!editMode} />
</div> </div>
<label htmlFor="name" className="text-2xl text-white mb-1 block font-montserrat"> <label htmlFor="name" className="text-xl lg:text-2xl text-white mb-1 block font-montserrat">
Nom d'utilisateur Nom d'utilisateur
</label> </label>
<input <input
@ -127,7 +127,7 @@ export default function Account() {
disabled={!editMode} disabled={!editMode}
/> />
<label htmlFor="email" className="text-2xl text-white mb-1 mt-4 block font-montserrat"> <label htmlFor="email" className="text-xl lg:text-2xl text-white mb-1 mt-4 block font-montserrat">
Adresse e-mail Adresse e-mail
</label> </label>
<input <input
@ -142,7 +142,7 @@ export default function Account() {
{editMode && ( {editMode && (
<> <>
<label htmlFor="password" className="text-2xl text-white mb-1 mt-4 block font-montserrat"> <label htmlFor="password" className="text-xl lg:text-2xl text-white mb-1 mt-4 block font-montserrat">
Mot de passe Mot de passe
</label> </label>
<input <input
@ -155,7 +155,7 @@ export default function Account() {
disabled={!editMode} disabled={!editMode}
/> />
<label htmlFor="confirm-password" className="text-2xl text-white mb-1 mt-4 block font-montserrat"> <label htmlFor="confirm-password" className="text-xl lg:text-2xl text-white mb-1 mt-4 block font-montserrat">
Confirmer le mot de passe Confirmer le mot de passe
</label> </label>
<input <input
@ -178,14 +178,14 @@ export default function Account() {
<div> <div>
<button <button
type="button" type="button"
className="bg-primary p-3 rounded-sm text-white font-montserrat text-2xl font-black cursor-pointer" className="bg-primary p-3 rounded-sm text-white font-montserrat text-lg text-2xl font-black cursor-pointer"
onClick={handleUpdateUser} onClick={handleUpdateUser}
> >
Enregistrer Enregistrer
</button> </button>
<button <button
type="button" type="button"
className="bg-red-500 p-3 rounded-sm text-white font-montserrat text-2xl font-black cursor-pointer ml-3" className="bg-red-500 p-3 rounded-sm text-white font-montserrat text-lg lg:text-2xl font-bold cursor-pointer ml-3"
onClick={() => setEditMode(!editMode)} onClick={() => setEditMode(!editMode)}
> >
Annuler Annuler
@ -194,7 +194,7 @@ export default function Account() {
) : ( ) : (
<button <button
type="button" type="button"
className="bg-primary p-3 rounded-sm text-white font-montserrat text-2xl font-black cursor-pointer" className="bg-primary p-3 rounded-sm text-white font-montserrat text-lg lg:text-2xl font-bold cursor-pointer"
onClick={() => setEditMode(!editMode)} onClick={() => setEditMode(!editMode)}
> >
Modifier le profil Modifier le profil
@ -206,13 +206,13 @@ export default function Account() {
{ /* Right side */} { /* Right side */}
<div className="w-2/3 flex flex-col items-start pl-10"> <div className="lg:w-2/3 flex flex-col items-start lg:pl-10 mt-8 lg:mt-0">
{/* Channel */} {/* Channel */}
{userChannel ? ( {userChannel ? (
<div className="glassmorphism p-10 w-full flex justify-between"> <div className="glassmorphism p-10 w-full flex justify-between">
<p className="text-3xl text-white mb-2 font-montserrat font-bold">{userChannel.channel.name}</p> <p className="text-xl lg:text-3xl text-white mb-2 font-montserrat font-bold">{userChannel.channel.name}</p>
<button> <button>
<span onClick={() => navigation(`/manage-channel/${userChannel.channel.id}`)} className="bg-primary p-3 rounded-sm text-white font-montserrat text-2xl font-semibold cursor-pointer"> <span onClick={() => navigation(`/manage-channel/${userChannel.channel.id}`)} className="bg-primary p-3 rounded-sm text-white font-montserrat text-lg lg:text-2xl font-semibold cursor-pointer">
Gérer la chaîne Gérer la chaîne
</span> </span>
</button> </button>
@ -235,7 +235,7 @@ export default function Account() {
Créer une playlist Créer une playlist
</button> </button>
</div> </div>
<div className="grid grid-cols-3 gap-8 mt-8" > <div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-8" >
{ {
userPlaylists && userPlaylists.map((playlist, index) => ( userPlaylists && userPlaylists.map((playlist, index) => (
<PlaylistCard playlist={playlist} key={index} onClick={handlePlaylistClick} /> <PlaylistCard playlist={playlist} key={index} onClick={handlePlaylistClick} />
@ -244,7 +244,7 @@ export default function Account() {
</div> </div>
{/* History */} {/* History */}
<h2 className="font-montserrat font-bold text-3xl text-white mt-10" >Historique</h2> <h2 className="font-montserrat font-bold text-3xl text-white mt-10" >Historique</h2>
<div className="grid grid-cols-3 gap-8 mt-8" > <div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-8" >
{ {
userHistory && userHistory.map((video, index) => ( userHistory && userHistory.map((video, index) => (
<VideoCard video={video} /> <VideoCard video={video} />

4
frontend/src/pages/AddVideo.jsx

@ -147,7 +147,7 @@ export default function AddVideo() {
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <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} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-36 pt-[118px]"> <main className="px-5 lg:px-36 pt-[118px]">
<h1 className="font-montserrat text-2xl font-black text-white"> <h1 className="font-montserrat text-2xl font-black text-white">
Ajouter une vidéo Ajouter une vidéo
</h1> </h1>
@ -316,7 +316,7 @@ export default function AddVideo() {
</form> </form>
{/* Right side: Preview of the video being added */} {/* Right side: Preview of the video being added */}
<div className="flex-1 flex justify-center"> <div className="flex-1 hidden lg:flex justify-center">
<div className="glassmorphism p-4 rounded-lg"> <div className="glassmorphism p-4 rounded-lg">
<img <img
src={videoThumbnail ? URL.createObjectURL(videoThumbnail) : "https://placehold.co/1280x720"} alt={videoTitle} src={videoThumbnail ? URL.createObjectURL(videoThumbnail) : "https://placehold.co/1280x720"} alt={videoTitle}

48
frontend/src/pages/Channel.jsx

@ -57,37 +57,39 @@ export default function Channel() {
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <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} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="pt-[118px] px-36" > <main className="pt-[48px] lg:pt-[118px] px-5 lg:px-36" >
{/* Channel Header */} {/* Channel Header */}
<div className="glassmorphism p-4" > <div className="glassmorphism p-4" >
<div className="flex items-center gap-4" > <div className="lg:flex items-center gap-4" >
<img <img
src={channel && channel.picture} src={channel && channel.picture}
alt={channel && channel.name} alt={channel && channel.name}
className="w-[192px] aspect-square object-cover rounded-full border-2 border-white" className="w-[96px] lg:w-[192px] aspect-square object-cover rounded-full border-2 border-white"
/> />
<div> <div className="flex items-center" >
<h1 className="font-montserrat font-bold text-3xl text-white" >{channel && channel.name}</h1> <div>
<p className="font-montserrat font-medium text-xl text-white" >{channel && channel.subscriptions} abonné(es)</p> <h1 className="font-montserrat font-bold text-3xl text-white" >{channel && channel.name}</h1>
<p className="font-montserrat font-medium text-xl text-white" >{channel && channel.subscriptions} abonné(es)</p>
</div>
{
isSubscribedToChannel ? (
<button
className="ml-5 bg-primary text-white font-montserrat font-bold px-4 py-1 h-1/2 rounded-md cursor-pointer"
onClick={handleSubscribe}
>
se désabonner
</button>
) : (
<button
className="ml-5 bg-primary text-white font-montserrat font-bold px-4 py-1 h-1/2 rounded-md cursor-pointer"
onClick={handleSubscribe}
>
s'abonner
</button>
)
}
</div> </div>
{
isSubscribedToChannel ? (
<button
className="ml-5 bg-primary text-white font-montserrat font-bold px-4 py-2 rounded-md cursor-pointer"
onClick={handleSubscribe}
>
se désabonner
</button>
) : (
<button
className="ml-5 bg-primary text-white font-montserrat font-bold px-4 py-2 rounded-md cursor-pointer"
onClick={handleSubscribe}
>
s'abonner
</button>
)
}
</div> </div>
<h2 className="font-bold font-montserrat text-white text-xl mt-4" >Description</h2> <h2 className="font-bold font-montserrat text-white text-xl mt-4" >Description</h2>

10
frontend/src/pages/Home.jsx

@ -92,18 +92,22 @@ export default function Home() {
<h1 className="font-montserrat text-4xl lg:text-8xl font-black w-1200/1920 text-center text-white -translate-y-1/2"> <h1 className="font-montserrat text-4xl lg:text-8xl font-black w-1200/1920 text-center text-white -translate-y-1/2">
Regarder des vidéos comme jamais auparavant Regarder des vidéos comme jamais auparavant
</h1> </h1>
<div className="flex justify-center gap-28 -translate-y-[100px] mt-10">
<button className="bg-white text-black font-montserrat p-3 rounded-sm text-2xl font-bold">
<div className="flex justify-center gap-10 -translate-y-[100px] mt-10">
<button className="bg-white text-black font-montserrat p-3 rounded-sm text-xl lg:text-2xl font-bold">
<a href="/login"> <a href="/login">
<p>Se connecter</p> <p>Se connecter</p>
</a> </a>
</button> </button>
<button className="bg-primary p-3 rounded-sm text-white font-montserrat text-2xl font-bold cursor-pointer"> <button className="bg-primary p-3 rounded-sm text-white font-montserrat text-xl lg:text-2xl font-bold cursor-pointer">
<a href="/register"> <a href="/register">
<p>Créer un compte</p> <p>Créer un compte</p>
</a> </a>
</button> </button>
</div> </div>
</> </>
)} )}
</div> </div>

6
frontend/src/pages/ManageChannel.jsx

@ -68,10 +68,10 @@ export default function ManageChannel() {
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <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} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="pt-[118px] px-36 flex pb-10"> <main className="pt-[48px] lg:pt-[118px] px-5 lg:px-36 lg:flex pb-10">
{/* LEFT SIDE */} {/* LEFT SIDE */}
<form className="glassmorphism w-1/3 py-10 px-4 h-max"> <form className="glassmorphism lg:w-1/3 py-10 px-4 h-max">
<img src={user.picture} className="w-1/3 aspect-square object-cover rounded-full mx-auto" alt=""/> <img src={user.picture} className="w-1/3 aspect-square object-cover rounded-full mx-auto" alt=""/>
<label htmlFor="name" className={`text-2xl text-white mb-1 block font-montserrat ${editMode ? "block" : "hidden"} `}> <label htmlFor="name" className={`text-2xl text-white mb-1 block font-montserrat ${editMode ? "block" : "hidden"} `}>
Nom de chaine Nom de chaine
@ -131,7 +131,7 @@ export default function ManageChannel() {
</form> </form>
{/* RIGHT SIDE */} {/* RIGHT SIDE */}
<div className="w-2/3 pl-10" > <div className="lg:w-2/3 lg:pl-10 mt-4 lg:mt-0" >
{/* VIEW / SUBSCRIBERS STATS */} {/* VIEW / SUBSCRIBERS STATS */}
<div className="flex gap-4" > <div className="flex gap-4" >

12
frontend/src/pages/ManageVideo.jsx

@ -203,25 +203,25 @@ export default function ManageVideo() {
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-36 pb-36"> <main className="px-5 lg:px-36 pb-36">
{ /* GRAPHS */ } { /* GRAPHS */ }
<div> <div>
<div className="flex pt-[118px] gap-4" > <div className="pt-[48px] lg:flex lg:pt-[118px] gap-4" >
<LinearGraph <LinearGraph
dataToGraph={viewsPerDay} dataToGraph={viewsPerDay}
legend="Vues" legend="Vues"
className="glassmorphism flex-1 h-[300px] p-4" className="glassmorphism flex-1 lg:h-[300px] p-4"
/> />
<LinearGraph <LinearGraph
dataToGraph={likesPerDay} dataToGraph={likesPerDay}
legend="Likes" legend="Likes"
className="glassmorphism flex-1 h-[300px] p-4" className="glassmorphism flex-1 mt-4 lg:mt-0 lg:h-[300px] p-4"
borderColor="#FF073A" borderColor="#FF073A"
/> />
</div> </div>
<div className="flex gap-4 mt-4 "> <div className="lg:flex gap-4 mt-4 ">
{ /* LEFT SIDE */ } { /* LEFT SIDE */ }
<div className="flex-1" > <div className="flex-1" >
@ -389,7 +389,7 @@ export default function ManageVideo() {
{ /* RIGHT SIDE */ } { /* RIGHT SIDE */ }
<div className="flex-1"> <div className="flex-1 mt-4 lg:mt-0">
<div className="flex gap-4"> <div className="flex gap-4">
{ /* TOTAL VIEWS */ } { /* TOTAL VIEWS */ }

4
frontend/src/pages/Playlist.jsx

@ -51,7 +51,7 @@ export default function Playlist() {
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <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} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-36 w-full pt-[118px]"> <main className="px-5 lg:px-36 w-full pt-[48px] lg:pt-[118px]">
<h1 className="font-bold font-montserrat text-3xl text-white" >{playlist && playlist.name}</h1> <h1 className="font-bold font-montserrat text-3xl text-white" >{playlist && playlist.name}</h1>
{/* CONTROLS */} {/* CONTROLS */}
@ -70,7 +70,7 @@ export default function Playlist() {
</button> </button>
</div> </div>
<div className="grid grid-cols-4 gap-8 mt-12"> <div className="grid grid-cols-1 lg:grid-cols-4 gap-8 mt-12">
{ {
playlist && playlist.videos && playlist.videos.length > 0 ? playlist.videos.map(video => ( playlist && playlist.videos && playlist.videos.length > 0 ? playlist.videos.map(video => (
<VideoCard <VideoCard

4
frontend/src/pages/Search.jsx

@ -53,7 +53,7 @@ export default function Search() {
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={true} alerts={alerts} onCloseAlert={onCloseAlert}/> <Navbar isSearchPage={true} alerts={alerts} onCloseAlert={onCloseAlert}/>
<main className="px-36 pt-[118px]"> <main className="px-5 lg:px-36 pt-[48px] lg:pt-[118px]">
{/* MEGA SEARCH BAR */} {/* MEGA SEARCH BAR */}
<div className="flex items-center w-full gap-8" > <div className="flex items-center w-full gap-8" >
@ -109,7 +109,7 @@ export default function Search() {
</div> </div>
{/* RESULTS */} {/* RESULTS */}
<div className="grid grid-cols-4 gap-8 mt-8"> <div className="grid grid-cols-1 lg:grid-cols-4 gap-8 mt-8">
{results && results.length > 0 ? ( {results && results.length > 0 ? (
results.map((result, index) => { results.map((result, index) => {
if (result.type === "video") { if (result.type === "video") {

134
frontend/src/pages/Video.jsx

@ -35,6 +35,7 @@ export default function Video() {
const [playlists, setPlaylists] = useState([]); const [playlists, setPlaylists] = useState([]);
const [isAddToPlaylistOpen, setIsAddToPlaylistOpen] = useState(false); const [isAddToPlaylistOpen, setIsAddToPlaylistOpen] = useState(false);
const [currentPlaylist, setCurrentPlaylist] = useState(null); const [currentPlaylist, setCurrentPlaylist] = useState(null);
const [isCommentVisible, setIsCommentVisible] = useState(window.innerWidth >= 1024); // Show comments by default on large screens
const fetchVideo = useCallback(async () => { const fetchVideo = useCallback(async () => {
// Fetch video data and similar videos based on the video ID from the URL // Fetch video data and similar videos based on the video ID from the URL
@ -307,11 +308,11 @@ export default function Video() {
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <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} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-36 w-full flex justify-between pt-[118px]"> <main className="px-5 lg:px-36 w-full lg:flex justify-between pt-[48px] lg:pt-[118px]">
{video ? ( {video ? (
<> <>
{/* Video player section */} {/* Video player section */}
<div className="w-1280/1920"> <div className="lg:w-1280/1920">
<div <div
className="relative w-full aspect-video mx-auto rounded-lg overflow-hidden" className="relative w-full aspect-video mx-auto rounded-lg overflow-hidden"
@ -326,6 +327,8 @@ export default function Video() {
onTimeUpdate={handleTimeUpdate} onTimeUpdate={handleTimeUpdate}
onLoadedMetadata={handleLoadedMetadata} onLoadedMetadata={handleLoadedMetadata}
onEnded={passToNextVideo} onEnded={passToNextVideo}
className="w-full h-full object-cover"
controls={window.innerWidth < 1024} // Show native controls on small screens
> >
<source src={`${video.file}`} type="video/mp4" /> <source src={`${video.file}`} type="video/mp4" />
Your browser does not support the video tag. Your browser does not support the video tag.
@ -333,7 +336,7 @@ export default function Video() {
{/* Video controls */} {/* Video controls */}
<div <div
className={`absolute bottom-4 left-4 right-4 glassmorphism-rounded-md p-4 flex items-center transition-opacity duration-300 ${ className={`absolute bottom-4 left-4 right-4 glassmorphism-rounded-md p-4 hidden lg:flex items-center transition-opacity duration-300 ${
showControls ? 'opacity-100' : 'opacity-0' showControls ? 'opacity-100' : 'opacity-0'
}`} }`}
ref={controllerRef} ref={controllerRef}
@ -367,54 +370,63 @@ export default function Video() {
<h1 className="mt-3 font-montserrat font-bold text-2xl text-white">{video.title}</h1> <h1 className="mt-3 font-montserrat font-bold text-2xl text-white">{video.title}</h1>
{/* Channel and like */} {/* Channel and like */}
<div className="flex items-center mt-4"> <div className="lg:flex items-center mt-4">
<img <div className="flex">
<img
src={video.creator?.profile_picture || "https://placehold.co/48"} src={video.creator?.profile_picture || "https://placehold.co/48"}
alt={video.creator?.name || "Creator"} alt={video.creator?.name || "Creator"}
className="w-12 h-12 rounded-full object-cover mr-3" className="w-12 h-12 rounded-full object-cover mr-3"
/> />
<div> <div>
<p className="text-white font-bold font-montserrat">{video.creator?.name}</p> <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> <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>
<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>
<button className="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>
<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">
playlists.length > 0 && isAddToPlaylistOpen && ( <button className="lg:ml-4 cursor-pointer" onClick={handleLike}>
<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"> <svg width="32" height="32" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<ul className="flex flex-col gap-2"> <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"/>
{playlists.map((playlist) => ( </svg>
<li </button>
key={playlist.id} <p className="font-montserrat text-white ml-2" >{video.likes}</p>
className="text-white font-montserrat font-medium text-sm cursor-pointer hover:underline flex items-center justify-between gap-4"
onClick={() => handleAddToPlaylist(playlist.id)} {
> isAuthenticated && (
<p className="text-start">{playlist.name}</p> <button className="relative ml-14">
<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 className="bg-primary cursor-pointer px-4 py-2 rounded-md flex items-center gap-4" onClick={() => { setIsAddToPlaylistOpen(!isAddToPlaylistOpen) }} >
</li> <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>
</ul> </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>
) )
} }
</button> </button>
)
}
</div>
</div> </div>
{/* Video details */} {/* Video details */}
@ -436,7 +448,12 @@ export default function Video() {
{/* Comments section */} {/* Comments section */}
<div> <div>
<h2 className="font-montserrat text-white text-2xl mt-8">Commentaires</h2> <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 <textarea
className="glassmorphism h-[100px] w-full font-inter text-white placeholder:text-[#9f9f9f] focus:outline-none py-4 px-6" className="glassmorphism h-[100px] w-full font-inter text-white placeholder:text-[#9f9f9f] focus:outline-none py-4 px-6"
placeholder="Ajouter un commentaire..." placeholder="Ajouter un commentaire..."
@ -449,15 +466,19 @@ export default function Video() {
</button> </button>
{/* Comments list */} {/* Comments list */}
<div className="mt-4"> {
{video.comments && video.comments.length > 0 ? ( isCommentVisible && (
video.comments.map((comment, index) => ( <div className="mt-4">
<Comment comment={comment} index={index} videoId={id} key={index} refetchVideo={fetchVideo} addAlert={addAlert} /> {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> <p className="text-gray-400">Aucun commentaire pour le moment. Soyez le premier à en publier !</p>
)}
</div>
)
}
</div> </div>
@ -469,15 +490,16 @@ export default function Video() {
{ {
!isPlaylist ? ( !isPlaylist ? (
<div className="flex flex-col items-center gap-2"> <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.map((video, index) => ( {similarVideos.map((video, index) => (
<div className="w-9/10" key={index}> <div className="w-full lg:w-9/10" key={index}>
<VideoCard video={video} /> <VideoCard video={video} />
</div> </div>
))} ))}
</div> </div>
) : ( ) : (
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<div className="glassmorphism w-9/10 py-4 px-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> <h2 className="font-montserrat text-white text-2xl">{currentPlaylist?.name}</h2>
{ {
currentPlaylist?.videos && currentPlaylist.videos.length > 0 ? ( currentPlaylist?.videos && currentPlaylist.videos.length > 0 ? (

Loading…
Cancel
Save