You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
16 KiB
362 lines
16 KiB
import Navbar from "../components/Navbar.jsx";
|
|
import { useEffect, useState } from "react";
|
|
import Tag from "../components/Tag.jsx";
|
|
import { getChannel, searchByUsername } from "../services/user.service.js";
|
|
import { uploadVideo, uploadThumbnail, uploadTags } from "../services/video.service.js";
|
|
import UserCard from "../components/UserCard.jsx";
|
|
import LoadingVideoModal from "../modals/LoadingVideoModal.jsx";
|
|
import { useNavigate } from "react-router-dom";
|
|
|
|
export default function AddVideo() {
|
|
|
|
const storedUser = localStorage.getItem("user");
|
|
const user = storedUser ? JSON.parse(storedUser) : null;
|
|
const token = localStorage.getItem("token");
|
|
const navigation = useNavigate();
|
|
|
|
const [videoTitle, setVideoTitle] = useState("");
|
|
const [videoDescription, setVideoDescription] = useState("");
|
|
const [videoTags, setVideoTags] = useState([]);
|
|
const [visibility, setVisibility] = useState("private");
|
|
const [videoThumbnail, setVideoThumbnail] = useState(null);
|
|
const [videoFile, setVideoFile] = useState(null);
|
|
const [channel, setChannel] = useState(null);
|
|
const [searchUser, setSearchUser] = useState("");
|
|
const [authorizedUsers, setAuthorizedUsers] = useState([]);
|
|
const [searchResults, setSearchResults] = useState([]);
|
|
const [alerts, setAlerts] = useState([]);
|
|
const [loadingState, setLoadingState] = useState("none");
|
|
const [loadingMessage, setLoadingMessage] = useState("");
|
|
|
|
useEffect(() => {
|
|
fetchChannel();
|
|
}, [])
|
|
|
|
const fetchChannel = async () => {
|
|
const fetchedChannel = await getChannel(user.id, token, addAlert);
|
|
setChannel(fetchedChannel.channel);
|
|
|
|
}
|
|
|
|
const handleTagKeyDown = (e) => {
|
|
if (e.key === 'Enter' && videoTags.length < 10) {
|
|
e.preventDefault();
|
|
const newTag = e.target.value.trim();
|
|
if (newTag && !videoTags.includes(newTag)) {
|
|
setVideoTags([...videoTags, newTag]);
|
|
e.target.value = '';
|
|
}
|
|
}
|
|
}
|
|
const handleTagRemove = (tagToRemove) => {
|
|
setVideoTags(videoTags.filter(tag => tag !== tagToRemove));
|
|
};
|
|
|
|
// This function handles the submission of the video form
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
|
|
console.log(channel)
|
|
setLoadingState("loading");
|
|
setLoadingMessage("Envoie de la vidéo...");
|
|
|
|
if (!videoTitle || !videoDescription || !videoThumbnail || !videoFile) {
|
|
addAlert('error', 'Veuillez remplir tous les champs requis.');
|
|
return;
|
|
}
|
|
if (!channel || !channel.id) {
|
|
addAlert('error', 'Chaîne non valide veuillez recharger la page.');
|
|
return;
|
|
}
|
|
if (videoTags.length > 10) {
|
|
addAlert('error', 'Vous ne pouvez pas ajouter plus de 10 tags.');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append("title", videoTitle);
|
|
formData.append("description", videoDescription);
|
|
formData.append("channel", channel.id.toString());
|
|
formData.append("visibility", visibility);
|
|
formData.append("authorizedUsers", JSON.stringify(authorizedUsers.map(user => user.id)));
|
|
formData.append("file", videoFile);
|
|
|
|
const request = await uploadVideo(formData, token, addAlert);
|
|
|
|
setLoadingMessage("Envoie de la miniature...");
|
|
|
|
// If the video was successfully created, we can now upload the thumbnail
|
|
const response = await request.json();
|
|
const videoId = response.id;
|
|
const thumbnailFormData = new FormData();
|
|
thumbnailFormData.append("video", videoId);
|
|
thumbnailFormData.append("file", videoThumbnail);
|
|
thumbnailFormData.append("channel", channel.id.toString());
|
|
await uploadThumbnail(thumbnailFormData, token, addAlert);
|
|
|
|
setLoadingMessage("Envoie des tags...");
|
|
// if the thumbnail was successfully uploaded, we can send the tags
|
|
const body = {
|
|
tags: videoTags,
|
|
channel: channel.id.toString()
|
|
};
|
|
await uploadTags(body, videoId, token, addAlert);
|
|
// If everything is successful, redirect to the video management page
|
|
navigation("/manage-channel/" + channel.id);
|
|
addAlert('success', 'Vidéo ajoutée avec succès !');
|
|
|
|
|
|
};
|
|
|
|
const addAlert = (type, message) => {
|
|
const newAlert = { type, message, id: Date.now() }; // Add unique ID
|
|
setAlerts([...alerts, newAlert]);
|
|
};
|
|
|
|
const onCloseAlert = (alertToRemove) => {
|
|
setAlerts(alerts.filter(alert => alert !== alertToRemove));
|
|
};
|
|
|
|
const onUserSearch = (e) => {
|
|
const searchUser = e.target.value;
|
|
if (searchUser.trim() !== "") {
|
|
// Call the API to search for users
|
|
searchByUsername(searchUser, token, addAlert)
|
|
.then((results) => {
|
|
|
|
setSearchResults(results);
|
|
})
|
|
.catch((error) => {
|
|
addAlert('error', 'Erreur lors de la recherche d\'utilisateurs.');
|
|
});
|
|
} else {
|
|
setSearchResults([]);
|
|
}
|
|
}
|
|
|
|
const onAuthorizedUserAdd = (user) => {
|
|
|
|
// Verify if user is not already authorized
|
|
if (authorizedUsers.find((u) => u.id === user.id)) {
|
|
addAlert('error', 'Utilisateur déjà autorisé.');
|
|
setSearchUser("")
|
|
setSearchResults([]);
|
|
return;
|
|
}
|
|
|
|
setAuthorizedUsers([...authorizedUsers, user]);
|
|
setSearchResults([]);
|
|
setSearchUser("")
|
|
};
|
|
|
|
const onAuthorizedUserRemove = (user) => {
|
|
setAuthorizedUsers(authorizedUsers.filter((u) => u.id !== user.id));
|
|
};
|
|
|
|
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 pt-[118px]">
|
|
<h1 className="font-montserrat text-2xl font-black text-white">
|
|
Ajouter une vidéo
|
|
</h1>
|
|
|
|
<div className="flex gap-8 mt-8">
|
|
{/* Left side: Form for adding video details */}
|
|
<form className="flex-1">
|
|
<label className="block text-white text-xl font-montserrat font-semibold mb-2" htmlFor="videoTitle">Titre de la vidéo</label>
|
|
<input
|
|
type="text"
|
|
id="videoTitle"
|
|
name="videoTitle"
|
|
className="w-full p-2 mb-4 glassmorphism focus:outline-none font-inter text-xl text-white"
|
|
placeholder="Entrez le titre de la vidéo"
|
|
value={videoTitle}
|
|
onChange={(e) => setVideoTitle(e.target.value)}
|
|
required
|
|
/>
|
|
|
|
<label className="block text-white text-xl font-montserrat font-semibold mb-2" htmlFor="videoDescription">Description de la vidéo</label>
|
|
<textarea
|
|
id="videoDescription"
|
|
name="videoDescription"
|
|
className="w-full p-2 mb-4 glassmorphism focus:outline-none font-inter text-xl text-white"
|
|
placeholder="Entrez la description de la vidéo"
|
|
rows="4"
|
|
value={videoDescription}
|
|
onChange={(e) => setVideoDescription(e.target.value)}
|
|
required
|
|
></textarea>
|
|
|
|
<label className="block text-white text-xl font-montserrat font-semibold mb-1" htmlFor="videoDescription">Tags</label>
|
|
<input
|
|
type="text"
|
|
id="videoTags"
|
|
name="videoTags"
|
|
className="w-full p-2 mb-4 glassmorphism focus:outline-none font-inter text-xl text-white"
|
|
placeholder="Entrez les tags de la vidéo (entrée pour valider) 10 maximum"
|
|
onKeyDown={handleTagKeyDown}
|
|
|
|
/>
|
|
<div className="flex flex-wrap gap-2 mb-2">
|
|
{videoTags.map((tag, index) => (
|
|
<Tag tag={tag} doShowControls={true} key={index} onSuppress={() => handleTagRemove(tag)} />
|
|
))}
|
|
</div>
|
|
|
|
<label className="block text-white text-xl font-montserrat font-semibold mb-2" htmlFor="visibility">Visibilité</label>
|
|
<select
|
|
name="visibility"
|
|
id="visibility"
|
|
className="w-full p-2 mb-4 glassmorphism focus:outline-none font-inter text-xl text-white"
|
|
value={visibility}
|
|
onChange={(e) => setVisibility(e.target.value)}
|
|
>
|
|
<option value="public">Public</option>
|
|
<option value="private">Privé</option>
|
|
</select>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
visibility == "private" && (
|
|
<div className="mb-4 glassmorphism p-4">
|
|
<label className="block text-white text-xl font-montserrat font-semibold mb-2" htmlFor="authorizedUsers">Utilisateurs autorisés</label>
|
|
<div className="mb-4">
|
|
<input
|
|
type="text"
|
|
id="authorizedUsers"
|
|
name="authorizedUsers"
|
|
className="w-full p-2 mb-2 glassmorphism focus:outline-none font-inter text-xl text-white"
|
|
placeholder="Rechercher un utilisateur"
|
|
value={searchUser}
|
|
onChange={(e) => setSearchUser(e.target.value)}
|
|
onKeyDown={(e) => {
|
|
onUserSearch(e)
|
|
}}
|
|
/>
|
|
<div>
|
|
{searchResults && searchResults.map((user, index) => (
|
|
<UserCard
|
|
user={user}
|
|
onSubmit={onAuthorizedUserAdd}
|
|
doShowControls={true}
|
|
key={index}
|
|
control={
|
|
<button type="button" className="bg-primary text-white font-montserrat p-3 rounded-lg text-lg font-bold cursor-pointer">
|
|
ajouter
|
|
</button>
|
|
}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div className="max-h-40 overflow-y-auto">
|
|
{authorizedUsers.length > 0 ? authorizedUsers.map((user, index) => (
|
|
<UserCard
|
|
user={user}
|
|
onSubmit={onAuthorizedUserRemove}
|
|
doShowControls={true}
|
|
key={index}
|
|
control={
|
|
<button type="button" className="bg-red-500 text-white font-montserrat p-3 rounded-lg text-lg font-bold cursor-pointer">
|
|
supprimer
|
|
</button>
|
|
}
|
|
/>
|
|
)) : (
|
|
<p className="text-white">Aucun utilisateur autorisé</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<label className="block text-white text-xl font-montserrat font-semibold mb-2" htmlFor="videoThumbnail">Miniature</label>
|
|
<input
|
|
type="file"
|
|
id="videoThumbnail"
|
|
name="videoThumbnail"
|
|
className="w-full p-2 mb-4 glassmorphism focus:outline-none font-inter text-xl text-white"
|
|
accept="image/*"
|
|
onChange={(e) => setVideoThumbnail(e.target.files[0])}
|
|
required
|
|
/>
|
|
|
|
<label className="block text-white text-xl font-montserrat font-semibold mb-2" htmlFor="videoFile">Fichier vidéo</label>
|
|
<input
|
|
type="file"
|
|
id="videoFile"
|
|
name="videoFile"
|
|
className="w-full p-2 mb-4 glassmorphism focus:outline-none font-inter text-xl text-white"
|
|
accept="video/*"
|
|
onChange={(e) => setVideoFile(e.target.files[0])}
|
|
required
|
|
/>
|
|
|
|
<button
|
|
type="submit"
|
|
className="bg-primary text-white font-montserrat p-3 rounded-lg text-2xl font-bold w-full cursor-pointer"
|
|
onClick={(e) => { handleSubmit(e) }}
|
|
>
|
|
Ajouter la vidéo
|
|
</button>
|
|
|
|
</form>
|
|
|
|
{/* Right side: Preview of the video being added */}
|
|
<div className="flex-1 hidden lg:flex justify-center">
|
|
<div className="glassmorphism p-4 rounded-lg">
|
|
<img
|
|
src={videoThumbnail ? URL.createObjectURL(videoThumbnail) : "https://placehold.co/1280x720"} alt={videoTitle}
|
|
className="w-[480px] h-auto mb-4 rounded-lg"
|
|
/>
|
|
<h2 className="text-white text-xl font-montserrat font-semibold mb-2">{videoTitle || "Titre de la vidéo"}</h2>
|
|
|
|
<div className="glassmorphism p-4 rounded-sm">
|
|
<p className="text-white font-inter mb-2">
|
|
{videoDescription || "Description de la vidéo"}
|
|
</p>
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
{videoTags.length > 0 ? (
|
|
videoTags.map((tag, index) => (
|
|
<Tag tag={tag} doShowControls={false} key={index} />
|
|
))
|
|
) : (
|
|
<span className="text-gray-400">Aucun tag ajouté</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</main>
|
|
<LoadingVideoModal state={loadingState} message={loadingMessage} />
|
|
</div>
|
|
);
|
|
|
|
}
|