Compare commits

...

4 Commits

Author SHA1 Message Date
Whykioh 0248a8f784 Merge branch 'main' of https://git.viensonjette.fr/Whykioh/main-website 2026-04-27 09:48:57 +02:00
Whykioh f506fd4b32 Merge branch 'main' of https://github.com/whykorp/home-page 2026-04-27 09:41:58 +02:00
Whykioh 0109146731 test 2026-04-27 09:41:54 +02:00
Whykioh 5463a3220a aa 2026-04-27 08:47:35 +02:00
8 changed files with 905 additions and 904 deletions
+1
View File
@@ -0,0 +1 @@
AAAAAAAAAA
+200 -200
View File
@@ -1,201 +1,201 @@
<?php <?php
session_start(); session_start();
header('Content-Type: application/json'); header('Content-Type: application/json');
// --- CONFIGURATION BDD --- // --- CONFIGURATION BDD ---
$host = 'localhost'; $host = 'localhost';
$db = 'watchgether'; $db = 'watchgether';
$user = 'root'; $user = 'root';
$pass = ''; // Vide par défaut sur Wamp $pass = ''; // Vide par défaut sur Wamp
$options = [ $options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_EMULATE_PREPARES => false,
]; ];
$apiKey = '23af653f99d2e7ac884415805e7ca84c'; $apiKey = '23af653f99d2e7ac884415805e7ca84c';
try { try {
$pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8", $user, $pass, $options); $pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8", $user, $pass, $options);
} catch (Exception $e) { } catch (Exception $e) {
echo json_encode(['success' => false, 'error' => 'Connexion BDD échouée']); echo json_encode(['success' => false, 'error' => 'Connexion BDD échouée']);
exit; exit;
} }
// --- LECTURE DE L'INPUT --- // --- LECTURE DE L'INPUT ---
$json = file_get_contents('php://input'); $json = file_get_contents('php://input');
$data = json_decode($json, true); $data = json_decode($json, true);
if (!$data || !isset($data['action'])) { if (!$data || !isset($data['action'])) {
echo json_encode(['success' => false, 'error' => 'Aucune action spécifiée']); echo json_encode(['success' => false, 'error' => 'Aucune action spécifiée']);
exit; exit;
} }
$action = $data['action']; $action = $data['action'];
$params = $data['params'] ?? []; $params = $data['params'] ?? [];
// Simulation d'un utilisateur connecté (à remplacer par ton système de login plus tard) // Simulation d'un utilisateur connecté (à remplacer par ton système de login plus tard)
$current_user_id = $_SESSION['user_id'] ?? 1; $current_user_id = $_SESSION['user_id'] ?? 1;
switch ($action) { switch ($action) {
// --- RECHERCHE TMDB --- // --- RECHERCHE TMDB ---
case 'searchTMDB': case 'searchTMDB':
$query = urlencode($params['query']); $query = urlencode($params['query']);
$url = "https://api.themoviedb.org/3/search/multi?api_key=$apiKey&language=fr-FR&query=$query"; $url = "https://api.themoviedb.org/3/search/multi?api_key=$apiKey&language=fr-FR&query=$query";
$response = file_get_contents($url); $response = file_get_contents($url);
if ($response) { if ($response) {
echo $response; // On renvoie directement le JSON de TMDB au front echo $response; // On renvoie directement le JSON de TMDB au front
} else { } else {
echo json_encode(['success' => false, 'error' => 'Erreur API TMDB']); echo json_encode(['success' => false, 'error' => 'Erreur API TMDB']);
} }
exit; exit;
// --- GESTION DES FILMS --- // --- GESTION DES FILMS ---
case 'addMovie': case 'addMovie':
// On cherche si le film existe déjà pour cet utilisateur // On cherche si le film existe déjà pour cet utilisateur
$stmt = $pdo->prepare("SELECT id FROM movies WHERE tmdb_id = ? AND user_id = ?"); $stmt = $pdo->prepare("SELECT id FROM movies WHERE tmdb_id = ? AND user_id = ?");
$stmt->execute([$params['tmdb_id'], $current_user_id]); $stmt->execute([$params['tmdb_id'], $current_user_id]);
if ($stmt->fetch()) { if ($stmt->fetch()) {
echo json_encode(['success' => false, 'error' => 'Film déjà dans la liste']); echo json_encode(['success' => false, 'error' => 'Film déjà dans la liste']);
exit; exit;
} else { } else {
$stmt = $pdo->prepare("INSERT INTO movies (tmdb_id, titre, affiche_path, type, user_id, vu) VALUES (?, ?, ?, ?, ?, 0)"); $stmt = $pdo->prepare("INSERT INTO movies (tmdb_id, titre, affiche_path, type, user_id, vu) VALUES (?, ?, ?, ?, ?, 0)");
$success = $stmt->execute([ $success = $stmt->execute([
$params['tmdb_id'], $params['tmdb_id'],
$params['titre'], $params['titre'],
$params['affiche_path'], $params['affiche_path'],
$params['type'], // 'film' ou 'serie' $params['type'], // 'film' ou 'serie'
$current_user_id $current_user_id
]); ]);
echo json_encode(['success' => $success]); echo json_encode(['success' => $success]);
exit; exit;
} }
case 'getMyList': case 'getMyList':
// Récupère les films ajoutés par l'utilisateur // Récupère les films ajoutés par l'utilisateur
$stmt = $pdo->prepare("SELECT * FROM movies WHERE user_id = ? ORDER BY date_ajout DESC"); $stmt = $pdo->prepare("SELECT * FROM movies WHERE user_id = ? ORDER BY date_ajout DESC");
$stmt->execute([$current_user_id]); $stmt->execute([$current_user_id]);
echo json_encode(['success' => true, 'movies' => $stmt->fetchAll()]); echo json_encode(['success' => true, 'movies' => $stmt->fetchAll()]);
exit; exit;
case 'getCommonList': case 'getCommonList':
// LA MAGIE : On cherche les doublons de tmdb_id entre deux utilisateurs // LA MAGIE : On cherche les doublons de tmdb_id entre deux utilisateurs
// On part du principe que tu es l'ID 1 et ta copine l'ID 2 (à adapter) // On part du principe que tu es l'ID 1 et ta copine l'ID 2 (à adapter)
$partner_id = ($current_user_id == 1) ? 2 : 1; $partner_id = ($current_user_id == 1) ? 2 : 1;
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
SELECT m1.* FROM movies m1 SELECT m1.* FROM movies m1
INNER JOIN movies m2 ON m1.tmdb_id = m2.tmdb_id INNER JOIN movies m2 ON m1.tmdb_id = m2.tmdb_id
WHERE m1.user_id = ? AND m2.user_id = ? WHERE m1.user_id = ? AND m2.user_id = ?
"); ");
$stmt->execute([$current_user_id, $partner_id]); $stmt->execute([$current_user_id, $partner_id]);
echo json_encode(['success' => true, 'common_movies' => $stmt->fetchAll()]); echo json_encode(['success' => true, 'common_movies' => $stmt->fetchAll()]);
exit; exit;
case 'getPartnerList': case 'getPartnerList':
$partner_id = ($current_user_id == 1) ? 2 : 1; $partner_id = ($current_user_id == 1) ? 2 : 1;
$stmt = $pdo->prepare("SELECT * FROM movies WHERE user_id = ? ORDER BY date_ajout DESC"); $stmt = $pdo->prepare("SELECT * FROM movies WHERE user_id = ? ORDER BY date_ajout DESC");
$stmt->execute([$partner_id]); $stmt->execute([$partner_id]);
echo json_encode(['success' => true, 'movies' => $stmt->fetchAll()]); echo json_encode(['success' => true, 'movies' => $stmt->fetchAll()]);
exit; exit;
// --- ACTIONS SUR LE FILM --- // --- ACTIONS SUR LE FILM ---
case 'toggleViewed': case 'toggleViewed':
// Alterne entre vu (1) et non vu (0) // Alterne entre vu (1) et non vu (0)
$stmt = $pdo->prepare("UPDATE movies SET vu = !vu WHERE id = ?"); $stmt = $pdo->prepare("UPDATE movies SET vu = !vu WHERE id = ?");
$success = $stmt->execute([(int)$params['movie_id']]); $success = $stmt->execute([(int)$params['movie_id']]);
echo json_encode(['success' => $success]); echo json_encode(['success' => $success]);
exit; exit;
case 'deleteMovie': case 'deleteMovie':
$stmt = $pdo->prepare("DELETE FROM movies WHERE id = ? AND user_id = ?"); $stmt = $pdo->prepare("DELETE FROM movies WHERE id = ? AND user_id = ?");
$success = $stmt->execute([(int)$params['movie_id'], $current_user_id]); $success = $stmt->execute([(int)$params['movie_id'], $current_user_id]);
echo json_encode(['success' => $success]); echo json_encode(['success' => $success]);
exit; exit;
// --- COMMENTAIRES --- // --- COMMENTAIRES ---
case 'addComment': case 'addComment':
$stmt = $pdo->prepare("INSERT INTO commentaires (movie_id, user_id, contenu) VALUES (?, ?, ?)"); $stmt = $pdo->prepare("INSERT INTO commentaires (movie_id, user_id, contenu) VALUES (?, ?, ?)");
$success = $stmt->execute([ $success = $stmt->execute([
(int)$params['movie_id'], (int)$params['movie_id'],
$current_user_id, $current_user_id,
$params['text'] $params['text']
]); ]);
echo json_encode(['success' => $success]); echo json_encode(['success' => $success]);
exit; exit;
case 'getComments': case 'getComments':
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
SELECT c.*, u.pseudo FROM commentaires c SELECT c.*, u.pseudo FROM commentaires c
JOIN users u ON c.user_id = u.id JOIN users u ON c.user_id = u.id
WHERE c.movie_id = ? ORDER BY c.date_ajout DESC WHERE c.movie_id = ? ORDER BY c.date_ajout DESC
"); ");
$stmt->execute([(int)$params['movie_id']]); $stmt->execute([(int)$params['movie_id']]);
echo json_encode(['success' => true, 'comments' => $stmt->fetchAll()]); echo json_encode(['success' => true, 'comments' => $stmt->fetchAll()]);
exit; exit;
case 'getMovieDetails': case 'getMovieDetails':
$id = $params['id']; $id = $params['id'];
$type = $params['type']; // 'movie' ou 'tv' $type = $params['type']; // 'movie' ou 'tv'
// On demande les détails + les crédits (acteurs) en une seule fois // On demande les détails + les crédits (acteurs) en une seule fois
$url = "https://api.themoviedb.org/3/$type/$id?api_key=$apiKey&language=fr-FR&append_to_response=credits"; $url = "https://api.themoviedb.org/3/$type/$id?api_key=$apiKey&language=fr-FR&append_to_response=credits";
$response = file_get_contents($url); $response = file_get_contents($url);
echo $response; echo $response;
exit; exit;
case 'setStarsRating': case 'setStarsRating':
$stmt = $pdo->prepare("INSERT INTO movies (rating) VALUES (?) WHERE id = ?"); $stmt = $pdo->prepare("INSERT INTO movies (rating) VALUES (?) WHERE id = ?");
$success = $stmt->execute([ $success = $stmt->execute([
(int)$params['movie_id'], (int)$params['movie_id'],
(int)$params['rating'] (int)$params['rating']
]); ]);
echo json_encode(['success' => $success]); echo json_encode(['success' => $success]);
exit; exit;
case 'getStarsRating': case 'getStarsRating':
$stmt = $pdo->prepare("SELECT rating FROM movies WHERE id = ?"); $stmt = $pdo->prepare("SELECT rating FROM movies WHERE id = ?");
$stmt->execute([(int)$params['movie_id']]); $stmt->execute([(int)$params['movie_id']]);
$rating = $stmt->fetchColumn(); $rating = $stmt->fetchColumn();
echo json_encode(['success' => true, 'rating' => $rating]); echo json_encode(['success' => true, 'rating' => $rating]);
exit; exit;
case 'register': case 'register':
$pseudo = $params['pseudo']; $pseudo = $params['pseudo'];
$pass = password_hash($params['password'], PASSWORD_DEFAULT); $pass = password_hash($params['password'], PASSWORD_DEFAULT);
$stmt = $pdo->prepare("INSERT INTO users (pseudo, password) VALUES (?, ?)"); $stmt = $pdo->prepare("INSERT INTO users (pseudo, password) VALUES (?, ?)");
try { try {
$stmt->execute([$pseudo, $pass]); $stmt->execute([$pseudo, $pass]);
echo json_encode(['success' => true]); echo json_encode(['success' => true]);
} catch (Exception $e) { } catch (Exception $e) {
echo json_encode(['success' => false, 'error' => 'Pseudo déjà pris']); echo json_encode(['success' => false, 'error' => 'Pseudo déjà pris']);
} }
exit; exit;
case 'login': case 'login':
$stmt = $pdo->prepare("SELECT * FROM users WHERE pseudo = ?"); $stmt = $pdo->prepare("SELECT * FROM users WHERE pseudo = ?");
$stmt->execute([$params['pseudo']]); $stmt->execute([$params['pseudo']]);
$user = $stmt->fetch(); $user = $stmt->fetch();
if ($user && password_verify($params['password'], $user['password'])) { if ($user && password_verify($params['password'], $user['password'])) {
$_SESSION['user_id'] = $user['id']; $_SESSION['user_id'] = $user['id'];
$_SESSION['pseudo'] = $user['pseudo']; $_SESSION['pseudo'] = $user['pseudo'];
echo json_encode(['success' => true]); echo json_encode(['success' => true]);
} else { } else {
echo json_encode(['success' => false, 'error' => 'Identifiants incorrects']); echo json_encode(['success' => false, 'error' => 'Identifiants incorrects']);
} }
exit; exit;
case 'logout': case 'logout':
session_destroy(); session_destroy();
echo json_encode(['success' => true]); echo json_encode(['success' => true]);
exit; exit;
default: default:
echo json_encode(['success' => false, 'error' => 'Action inconnue']); echo json_encode(['success' => false, 'error' => 'Action inconnue']);
exit; exit;
} }
+42 -42
View File
@@ -1,43 +1,43 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>WatchGether - Connexion</title> <title>WatchGether - Connexion</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
</head> </head>
<body class="bg-slate-900 text-white flex items-center justify-center min-h-screen"> <body class="bg-slate-900 text-white flex items-center justify-center min-h-screen">
<div class="bg-slate-800 p-8 rounded-2xl shadow-xl w-full max-w-md border border-white/5"> <div class="bg-slate-800 p-8 rounded-2xl shadow-xl w-full max-w-md border border-white/5">
<h1 class="text-3xl font-black text-center mb-8 text-blue-400 italic">WATCHGETHER</h1> <h1 class="text-3xl font-black text-center mb-8 text-blue-400 italic">WATCHGETHER</h1>
<div id="authForm"> <div id="authForm">
<input type="text" id="pseudo" placeholder="Pseudo" class="w-full p-4 mb-4 bg-slate-900 rounded-lg border border-slate-700 focus:border-blue-500 outline-none"> <input type="text" id="pseudo" placeholder="Pseudo" class="w-full p-4 mb-4 bg-slate-900 rounded-lg border border-slate-700 focus:border-blue-500 outline-none">
<input type="password" id="password" placeholder="Mot de passe" class="w-full p-4 mb-6 bg-slate-900 rounded-lg border border-slate-700 focus:border-blue-500 outline-none"> <input type="password" id="password" placeholder="Mot de passe" class="w-full p-4 mb-6 bg-slate-900 rounded-lg border border-slate-700 focus:border-blue-500 outline-none">
<button onclick="handleAuth('login')" class="w-full bg-blue-600 hover:bg-blue-500 py-4 rounded-xl font-bold mb-4 transition">Se connecter</button> <button onclick="handleAuth('login')" class="w-full bg-blue-600 hover:bg-blue-500 py-4 rounded-xl font-bold mb-4 transition">Se connecter</button>
<button onclick="handleAuth('register')" class="w-full bg-slate-700 hover:bg-slate-600 py-4 rounded-xl font-bold transition">Créer un compte</button> <button onclick="handleAuth('register')" class="w-full bg-slate-700 hover:bg-slate-600 py-4 rounded-xl font-bold transition">Créer un compte</button>
</div> </div>
</div> </div>
<script> <script>
async function handleAuth(action) { async function handleAuth(action) {
const pseudo = document.getElementById('pseudo').value; const pseudo = document.getElementById('pseudo').value;
const password = document.getElementById('password').value; const password = document.getElementById('password').value;
const response = await fetch('RequestHandler.php', { const response = await fetch('RequestHandler.php', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, params: { pseudo, password } }) body: JSON.stringify({ action, params: { pseudo, password } })
}); });
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
if (action === 'register') alert("Compte créé ! Connecte-toi maintenant."); if (action === 'register') alert("Compte créé ! Connecte-toi maintenant.");
else window.location.href = 'search/index.php'; else window.location.href = 'search/index.php';
} else { } else {
alert(data.error); alert(data.error);
} }
} }
</script> </script>
</body> </body>
</html> </html>
+299 -299
View File
@@ -1,300 +1,300 @@
let allMovies = []; // Stockage local de la liste entière let allMovies = []; // Stockage local de la liste entière
window.onload = () => { window.onload = () => {
loadList(); loadList();
document.getElementById('sortOrder').addEventListener('change', renderList); document.getElementById('sortOrder').addEventListener('change', renderList);
document.getElementById('filterType').addEventListener('change', renderList); document.getElementById('filterType').addEventListener('change', renderList);
document.getElementById('filterStatus').addEventListener('change', renderList); document.getElementById('filterStatus').addEventListener('change', renderList);
} }
async function apiRequest(action, params = {}) { async function apiRequest(action, params = {}) {
try { try {
const response = await fetch('../RequestHandler.php', { const response = await fetch('../RequestHandler.php', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ action, params }) body: JSON.stringify({ action, params })
}); });
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Erreur API:", error); console.error("Erreur API:", error);
return { success: false, error: "Erreur de connexion au serveur" }; return { success: false, error: "Erreur de connexion au serveur" };
} }
} }
async function addMovie(tmdbId, title, posterPath, type) { async function addMovie(tmdbId, title, posterPath, type) {
const result = await apiRequest('addMovie', { const result = await apiRequest('addMovie', {
tmdb_id: tmdbId, tmdb_id: tmdbId,
titre: title, titre: title,
affiche_path: posterPath, affiche_path: posterPath,
type: type type: type
}); });
if (result.success) { if (result.success) {
alert(`"${title}" a été ajouté à votre liste ! 🍿`); alert(`"${title}" a été ajouté à votre liste ! 🍿`);
closeModal(); closeModal();
} else { } else {
alert('Erreur lors de l\'ajout. Vérifie si le film n\'est pas déjà présent.'); alert('Erreur lors de l\'ajout. Vérifie si le film n\'est pas déjà présent.');
} }
} }
async function loadList() { async function loadList() {
const response = await fetch('../RequestHandler.php', { const response = await fetch('../RequestHandler.php', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'getMyList' }) body: JSON.stringify({ action: 'getMyList' })
}); });
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
allMovies = data.movies; allMovies = data.movies;
renderList(); renderList();
} }
} }
function renderList() { function renderList() {
const grid = document.getElementById('myListGrid'); const grid = document.getElementById('myListGrid');
const sort = document.getElementById('sortOrder').value; const sort = document.getElementById('sortOrder').value;
const type = document.getElementById('filterType').value; const type = document.getElementById('filterType').value;
const status = document.getElementById('filterStatus').value; const status = document.getElementById('filterStatus').value;
// 1. Filtrage // 1. Filtrage
let filtered = allMovies.filter(m => { let filtered = allMovies.filter(m => {
const matchType = (type === 'all' || m.type === type); const matchType = (type === 'all' || m.type === type);
const matchStatus = (status === 'all' || m.vu == status); const matchStatus = (status === 'all' || m.vu == status);
return matchType && matchStatus; return matchType && matchStatus;
}); });
// 2. Tri // 2. Tri
filtered.sort((a, b) => { filtered.sort((a, b) => {
if (sort === 'titre_asc') return a.titre.localeCompare(b.titre); if (sort === 'titre_asc') return a.titre.localeCompare(b.titre);
if (sort === 'date_ajout_asc') return new Date(a.date_ajout) - new Date(b.date_ajout); if (sort === 'date_ajout_asc') return new Date(a.date_ajout) - new Date(b.date_ajout);
if (sort === 'date_ajout_desc') return new Date(b.date_ajout) - new Date(a.date_ajout); if (sort === 'date_ajout_desc') return new Date(b.date_ajout) - new Date(a.date_ajout);
}); });
// 3. Affichage // 3. Affichage
grid.innerHTML = ''; grid.innerHTML = '';
document.getElementById('movieCount').innerText = filtered.length; document.getElementById('movieCount').innerText = filtered.length;
filtered.forEach(m => { filtered.forEach(m => {
const card = document.createElement('div'); const card = document.createElement('div');
// On ajoute 'flex flex-col' pour que le contenu s'empile proprement // On ajoute 'flex flex-col' pour que le contenu s'empile proprement
card.className = `relative group bg-slate-800 rounded-xl overflow-hidden border ${m.vu == 1 ? 'border-green-500/50' : 'border-slate-700'} flex flex-col h-full`; card.className = `relative group bg-slate-800 rounded-xl overflow-hidden border ${m.vu == 1 ? 'border-green-500/50' : 'border-slate-700'} flex flex-col h-full`;
if (currentView === 'partner') { if (currentView === 'partner') {
card.innerHTML = ` card.innerHTML = `
<div class="relative aspect-[2/3] overflow-hidden cursor-pointer" onclick="showDetails(${m.tmdb_id}, '${m.type}', ${m.id}, ${m.vu})"> <div class="relative aspect-[2/3] overflow-hidden cursor-pointer" onclick="showDetails(${m.tmdb_id}, '${m.type}', ${m.id}, ${m.vu})">
<img src="https://image.tmdb.org/t/p/w500${m.affiche_path}" <img src="https://image.tmdb.org/t/p/w500${m.affiche_path}"
class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}"> class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}">
</div> </div>
<div class="p-3 flex flex-col justify-between flex-grow bg-slate-800"> <div class="p-3 flex flex-col justify-between flex-grow bg-slate-800">
<h3 class="font-bold text-sm text-white truncate mb-2" title="${m.titre}">${m.titre}</h3> <h3 class="font-bold text-sm text-white truncate mb-2" title="${m.titre}">${m.titre}</h3>
<div class="flex gap-2 mt-auto"> <div class="flex gap-2 mt-auto">
<button onclick="addMovie(${m.tmdb_id}, '${m.titre}', '${m.affiche_path}', '${m.type}')" class="w-full bg-blue-600 hover:bg-blue-500 text-white py-4 rounded-xl font-black shadow-lg transition-all active:scale-95"> <button onclick="addMovie(${m.tmdb_id}, '${m.titre}', '${m.affiche_path}', '${m.type}')" class="w-full bg-blue-600 hover:bg-blue-500 text-white py-4 rounded-xl font-black shadow-lg transition-all active:scale-95">
+ AJOUTER À LA LISTE + AJOUTER À LA LISTE
</button> </button>
</div> </div>
</div> </div>
`; `;
} else if (currentView === 'common') { } else if (currentView === 'common') {
card.innerHTML = ` card.innerHTML = `
<div class="relative aspect-[2/3] overflow-hidden cursor-pointer" onclick="showDetails(${m.tmdb_id}, '${m.type}', ${m.id}, ${m.vu})"> <div class="relative aspect-[2/3] overflow-hidden cursor-pointer" onclick="showDetails(${m.tmdb_id}, '${m.type}', ${m.id}, ${m.vu})">
<img src="https://image.tmdb.org/t/p/w500${m.affiche_path}" <img src="https://image.tmdb.org/t/p/w500${m.affiche_path}"
class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}"> class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}">
</div> </div>
<div class="p-3 flex flex-col justify-between flex-grow bg-slate-800"> <div class="p-3 flex flex-col justify-between flex-grow bg-slate-800">
<h3 class="font-bold text-sm text-white truncate mb-2" title="${m.titre}">${m.titre}</h3> <h3 class="font-bold text-sm text-white truncate mb-2" title="${m.titre}">${m.titre}</h3>
<div class="flex gap-2 mt-auto"> <div class="flex gap-2 mt-auto">
<button onclick="toggleVu(${m.id})" class="flex-grow py-2 rounded text-[10px] font-black tracking-wider transition-colors ${m.vu == 1 ? 'bg-green-600 text-white' : 'bg-slate-700 text-gray-300 hover:bg-slate-600'}"> <button onclick="toggleVu(${m.id})" class="flex-grow py-2 rounded text-[10px] font-black tracking-wider transition-colors ${m.vu == 1 ? 'bg-green-600 text-white' : 'bg-slate-700 text-gray-300 hover:bg-slate-600'}">
${m.vu == 1 ? 'VU' : 'À VOIR'} ${m.vu == 1 ? 'VU' : 'À VOIR'}
</button> </button>
</div> </div>
</div> </div>
`; `;
} else { } else {
card.innerHTML = ` card.innerHTML = `
<div class="relative aspect-[2/3] overflow-hidden cursor-pointer" onclick="showDetails(${m.tmdb_id}, '${m.type}', ${m.id}, ${m.vu})"> <div class="relative aspect-[2/3] overflow-hidden cursor-pointer" onclick="showDetails(${m.tmdb_id}, '${m.type}', ${m.id}, ${m.vu})">
<img src="https://image.tmdb.org/t/p/w500${m.affiche_path}" <img src="https://image.tmdb.org/t/p/w500${m.affiche_path}"
class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}"> class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}">
</div> </div>
<div class="p-3 flex flex-col justify-between flex-grow bg-slate-800"> <div class="p-3 flex flex-col justify-between flex-grow bg-slate-800">
<h3 class="font-bold text-sm text-white truncate mb-2" title="${m.titre}">${m.titre}</h3> <h3 class="font-bold text-sm text-white truncate mb-2" title="${m.titre}">${m.titre}</h3>
<div class="flex gap-2 mt-auto"> <div class="flex gap-2 mt-auto">
<button onclick="showDetails(${m.tmdb_id}, '${m.type}', ${m.id}, ${m.vu})" class="flex-grow py-2 rounded text-[10px] font-black tracking-wider transition-colors bg-slate-700 text-gray-300 hover:bg-slate-600"> <button onclick="showDetails(${m.tmdb_id}, '${m.type}', ${m.id}, ${m.vu})" class="flex-grow py-2 rounded text-[10px] font-black tracking-wider transition-colors bg-slate-700 text-gray-300 hover:bg-slate-600">
Voir plus Voir plus
</button> </button>
<button onclick="deleteMovie(${m.id})" class="bg-red-900/30 hover:bg-red-600 text-red-500 hover:text-white px-3 py-2 rounded transition-colors text-[10px]"> <button onclick="deleteMovie(${m.id})" class="bg-red-900/30 hover:bg-red-600 text-red-500 hover:text-white px-3 py-2 rounded transition-colors text-[10px]">
🗑️ 🗑️
</button> </button>
</div> </div>
</div> </div>
`; `;
} }
grid.appendChild(card); grid.appendChild(card);
}); });
} }
// Actions rapides // Actions rapides
async function toggleVu(id) { async function toggleVu(id) {
const res = await fetch('../RequestHandler.php', { const res = await fetch('../RequestHandler.php', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'toggleViewed', params: { movie_id: id } }) body: JSON.stringify({ action: 'toggleViewed', params: { movie_id: id } })
}); });
const data = await res.json(); const data = await res.json();
if (data.success) { if (data.success) {
// On met à jour localement pour ne pas recharger toute l'API // On met à jour localement pour ne pas recharger toute l'API
const movie = allMovies.find(m => m.id == id); const movie = allMovies.find(m => m.id == id);
movie.vu = movie.vu == 1 ? 0 : 1; movie.vu = movie.vu == 1 ? 0 : 1;
renderList(); renderList();
} }
} }
async function deleteMovie(id) { async function deleteMovie(id) {
if(!confirm("Supprimer ce film de ta liste ?")) return; if(!confirm("Supprimer ce film de ta liste ?")) return;
const res = await fetch('../RequestHandler.php', { const res = await fetch('../RequestHandler.php', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'deleteMovie', params: { movie_id: id } }) body: JSON.stringify({ action: 'deleteMovie', params: { movie_id: id } })
}); });
const data = await res.json(); const data = await res.json();
if (data.success) { if (data.success) {
allMovies = allMovies.filter(m => m.id != id); allMovies = allMovies.filter(m => m.id != id);
renderList(); renderList();
} }
} }
let currentView = 'my'; // 'my', 'common', ou 'partner' let currentView = 'my'; // 'my', 'common', ou 'partner'
async function switchTab(view) { async function switchTab(view) {
currentView = view; currentView = view;
// Mise à jour visuelle des boutons // Mise à jour visuelle des boutons
const tabs = ['my', 'common', 'partner']; const tabs = ['my', 'common', 'partner'];
tabs.forEach(t => { tabs.forEach(t => {
const btn = document.getElementById(`tab-${t}`); const btn = document.getElementById(`tab-${t}`);
if (t === view) { if (t === view) {
btn.classList.replace('bg-slate-700/50', 'bg-slate-800'); btn.classList.replace('bg-slate-700/50', 'bg-slate-800');
btn.classList.replace('text-gray-400', 'text-white'); btn.classList.replace('text-gray-400', 'text-white');
btn.classList.replace('border-transparent', 'border-blue-500'); btn.classList.replace('border-transparent', 'border-blue-500');
} else { } else {
btn.classList.replace('bg-slate-800', 'bg-slate-700/50'); btn.classList.replace('bg-slate-800', 'bg-slate-700/50');
btn.classList.replace('text-white', 'text-gray-400'); btn.classList.replace('text-white', 'text-gray-400');
btn.classList.replace('border-blue-500', 'border-transparent'); btn.classList.replace('border-blue-500', 'border-transparent');
} }
}); });
// On recharge les données selon la vue // On recharge les données selon la vue
let action = 'getMyList'; let action = 'getMyList';
if (view === 'common') action = 'getCommonList'; if (view === 'common') action = 'getCommonList';
if (view === 'partner') action = 'getPartnerList'; // Il faudra créer cette action dans RequestHandler if (view === 'partner') action = 'getPartnerList'; // Il faudra créer cette action dans RequestHandler
const response = await apiRequest(action); const response = await apiRequest(action);
if (response.success) { if (response.success) {
allMovies = response.movies || response.common_movies; allMovies = response.movies || response.common_movies;
renderList(); renderList();
} }
} }
async function showDetails(tmdbId, type, localId = null, isVu = 0) { async function showDetails(tmdbId, type, localId = null, isVu = 0) {
// 1. On récupère les détails complets via TMDB (comme dans search.js) // 1. On récupère les détails complets via TMDB (comme dans search.js)
const mediaType = (type === 'serie') ? 'tv' : 'movie'; const mediaType = (type === 'serie') ? 'tv' : 'movie';
// On appelle ton RequestHandler pour avoir les détails + acteurs // On appelle ton RequestHandler pour avoir les détails + acteurs
const movie = await apiRequest('getMovieDetails', { id: tmdbId, type: mediaType }); const movie = await apiRequest('getMovieDetails', { id: tmdbId, type: mediaType });
if (!movie) return; if (!movie) return;
// 2. On remplit la modal avec les infos fraîches // 2. On remplit la modal avec les infos fraîches
document.getElementById('modalTitle').innerText = movie.title || movie.name; document.getElementById('modalTitle').innerText = movie.title || movie.name;
const year = (movie.release_date || movie.first_air_date || "").substring(0, 4); const year = (movie.release_date || movie.first_air_date || "").substring(0, 4);
const duration = movie.runtime ? `${movie.runtime} min` : (movie.number_of_seasons ? `${movie.number_of_seasons} Saison(s)` : ""); const duration = movie.runtime ? `${movie.runtime} min` : (movie.number_of_seasons ? `${movie.number_of_seasons} Saison(s)` : "");
const genres = movie.genres.map(g => g.name).join(', '); const genres = movie.genres.map(g => g.name).join(', ');
document.getElementById('modalMeta').innerText = `${year}${genres} ${duration ? '• ' + duration : ''}`; document.getElementById('modalMeta').innerText = `${year}${genres} ${duration ? '• ' + duration : ''}`;
document.getElementById('modalOverview').innerText = movie.overview || "Aucun synopsis disponible."; document.getElementById('modalOverview').innerText = movie.overview || "Aucun synopsis disponible.";
document.getElementById('modalVote').innerText = movie.vote_average ? movie.vote_average.toFixed(1) : "N/A"; document.getElementById('modalVote').innerText = movie.vote_average ? movie.vote_average.toFixed(1) : "N/A";
// Image de bannière // Image de bannière
const bannerUrl = movie.backdrop_path ? `https://image.tmdb.org/t/p/original${movie.backdrop_path}` : ''; const bannerUrl = movie.backdrop_path ? `https://image.tmdb.org/t/p/original${movie.backdrop_path}` : '';
document.getElementById('modalBanner').style.backgroundImage = `url(${bannerUrl})`; document.getElementById('modalBanner').style.backgroundImage = `url(${bannerUrl})`;
// Réalisateur // Réalisateur
const director = movie.credits.crew.find(person => person.job === 'Director'); const director = movie.credits.crew.find(person => person.job === 'Director');
document.getElementById('modalDirector').innerText = director ? director.name : "Non renseigné"; document.getElementById('modalDirector').innerText = director ? director.name : "Non renseigné";
// Casting // Casting
const castContainer = document.getElementById('modalCast'); const castContainer = document.getElementById('modalCast');
castContainer.innerHTML = movie.credits.cast.slice(0, 8).map(actor => ` castContainer.innerHTML = movie.credits.cast.slice(0, 8).map(actor => `
<div class="min-w-[110px] text-center flex-shrink-0"> <div class="min-w-[110px] text-center flex-shrink-0">
<img src="${actor.profile_path ? 'https://image.tmdb.org/t/p/w185' + actor.profile_path : 'https://via.placeholder.com/185x278?text=No+Image'}" <img src="${actor.profile_path ? 'https://image.tmdb.org/t/p/w185' + actor.profile_path : 'https://via.placeholder.com/185x278?text=No+Image'}"
class="w-20 h-20 object-cover rounded-full mx-auto mb-2 border-2 border-slate-700 shadow-lg"> class="w-20 h-20 object-cover rounded-full mx-auto mb-2 border-2 border-slate-700 shadow-lg">
<p class="text-[10px] font-bold text-white">${actor.name}</p> <p class="text-[10px] font-bold text-white">${actor.name}</p>
</div> </div>
`).join(''); `).join('');
// 3. LOGIQUE DU BOUTON (Vu / Pas Vu) // 3. LOGIQUE DU BOUTON (Vu / Pas Vu)
const btn = document.getElementById('modalMainBtn'); const btn = document.getElementById('modalMainBtn');
const ratingZone = document.getElementById('ratingZone'); const ratingZone = document.getElementById('ratingZone');
if (localId) { if (localId) {
ratingZone.classList.remove('hidden'); // On montre les étoiles ratingZone.classList.remove('hidden'); // On montre les étoiles
if (isVu == 1) { if (isVu == 1) {
btn.innerText = "NE PLUS MARQUER COMME VU"; btn.innerText = "NE PLUS MARQUER COMME VU";
btn.className = "w-full bg-slate-700 hover:bg-slate-600 text-white py-4 rounded-xl font-black transition-all"; btn.className = "w-full bg-slate-700 hover:bg-slate-600 text-white py-4 rounded-xl font-black transition-all";
} else { } else {
btn.innerText = "MARQUER COMME VU"; btn.innerText = "MARQUER COMME VU";
btn.className = "w-full bg-green-600 hover:bg-green-500 text-white py-4 rounded-xl font-black transition-all"; btn.className = "w-full bg-green-600 hover:bg-green-500 text-white py-4 rounded-xl font-black transition-all";
} }
btn.onclick = async () => { btn.onclick = async () => {
await toggleVu(localId); // Ta fonction qui change le statut en BDD await toggleVu(localId); // Ta fonction qui change le statut en BDD
closeModal(); closeModal();
}; };
} }
// 4. LOGIQYUE DES ÉTOILES // 4. LOGIQYUE DES ÉTOILES
// On récupère la note actuelle pour ce film (si elle existe) // On récupère la note actuelle pour ce film (si elle existe)
apiRequest('getStarsRating', { movie_id: localId }).then(res => { apiRequest('getStarsRating', { movie_id: localId }).then(res => {
if (res.success) { if (res.success) {
const currentRating = res.rating || 0; const currentRating = res.rating || 0;
setRating(currentRating); // On affiche la note actuelle setRating(currentRating); // On affiche la note actuelle
} }
}); });
// Affichage final // Affichage final
document.getElementById('movieModal').classList.remove('hidden'); document.getElementById('movieModal').classList.remove('hidden');
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
} }
// Fonction pour les étoiles // Fonction pour les étoiles
function setRating(note) { function setRating(note) {
const currentMovieId = document.getElementById('modalMainBtn').onclick.toString().match(/toggleVu\((\d+)\)/)[1]; // Extraction de l'ID du film const currentMovieId = document.getElementById('modalMainBtn').onclick.toString().match(/toggleVu\((\d+)\)/)[1]; // Extraction de l'ID du film
const stars = document.querySelectorAll('#starContainer span'); const stars = document.querySelectorAll('#starContainer span');
stars.forEach((star, index) => { stars.forEach((star, index) => {
if (index < note) { if (index < note) {
star.classList.replace('text-gray-600', 'text-yellow-400'); star.classList.replace('text-gray-600', 'text-yellow-400');
} else { } else {
star.classList.replace('text-yellow-400', 'text-gray-600'); star.classList.replace('text-yellow-400', 'text-gray-600');
} }
}); });
apiRequest('setStarsRating', { movie_id: currentMovieId, rating: note }); // currentMovieId doit être défini lors de l'ouverture de la modal apiRequest('setStarsRating', { movie_id: currentMovieId, rating: note }); // currentMovieId doit être défini lors de l'ouverture de la modal
} }
function closeModal() { function closeModal() {
document.getElementById('movieModal').classList.add('hidden'); document.getElementById('movieModal').classList.add('hidden');
document.body.style.overflow = 'auto'; document.body.style.overflow = 'auto';
} }
loadList(); loadList();
+179 -179
View File
@@ -1,180 +1,180 @@
// --- CONFIGURATION & UTILITAIRES --- // --- CONFIGURATION & UTILITAIRES ---
/** /**
* Fonction universelle pour parler à ton RequestHandler * Fonction universelle pour parler à ton RequestHandler
* @param {string} action - L'action à exécuter (ex: 'searchTMDB') * @param {string} action - L'action à exécuter (ex: 'searchTMDB')
* @param {object} params - Les données à envoyer * @param {object} params - Les données à envoyer
*/ */
async function apiRequest(action, params = {}) { async function apiRequest(action, params = {}) {
try { try {
const response = await fetch('../RequestHandler.php', { const response = await fetch('../RequestHandler.php', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ action, params }) body: JSON.stringify({ action, params })
}); });
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Erreur API:", error); console.error("Erreur API:", error);
return { success: false, error: "Erreur de connexion au serveur" }; return { success: false, error: "Erreur de connexion au serveur" };
} }
} }
document.getElementById('ratingZone').classList.add('hidden'); document.getElementById('ratingZone').classList.add('hidden');
// --- RECHERCHE --- // --- RECHERCHE ---
/** /**
* Lance la recherche TMDB et affiche les résultats sous forme de cartes * Lance la recherche TMDB et affiche les résultats sous forme de cartes
*/ */
async function performSearch() { async function performSearch() {
const query = document.getElementById('searchInput').value; const query = document.getElementById('searchInput').value;
if (!query) return; if (!query) return;
const grid = document.getElementById('resultsGrid'); const grid = document.getElementById('resultsGrid');
grid.innerHTML = '<p class="col-span-full text-center py-10 opacity-50">Recherche de pépites en cours...</p>'; grid.innerHTML = '<p class="col-span-full text-center py-10 opacity-50">Recherche de pépites en cours...</p>';
const data = await apiRequest('searchTMDB', { query: query }); const data = await apiRequest('searchTMDB', { query: query });
grid.innerHTML = ''; // On vide grid.innerHTML = ''; // On vide
if (data.results && data.results.length > 0) { if (data.results && data.results.length > 0) {
data.results.forEach(item => { data.results.forEach(item => {
// On ignore les résultats sans image ou qui ne sont pas film/série // On ignore les résultats sans image ou qui ne sont pas film/série
if (!item.poster_path || (item.media_type !== 'movie' && item.media_type !== 'tv')) return; if (!item.poster_path || (item.media_type !== 'movie' && item.media_type !== 'tv')) return;
const title = item.title || item.name; const title = item.title || item.name;
const poster = `https://image.tmdb.org/t/p/w500${item.poster_path}`; const poster = `https://image.tmdb.org/t/p/w500${item.poster_path}`;
const type = item.media_type === 'tv' ? 'serie' : 'film'; const type = item.media_type === 'tv' ? 'serie' : 'film';
const year = (item.release_date || item.first_air_date || "").substring(0, 4); const year = (item.release_date || item.first_air_date || "").substring(0, 4);
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'bg-slate-800 rounded-xl overflow-hidden border border-slate-700 hover:scale-105 transition-transform cursor-pointer group'; card.className = 'bg-slate-800 rounded-xl overflow-hidden border border-slate-700 hover:scale-105 transition-transform cursor-pointer group';
card.innerHTML = ` card.innerHTML = `
<div class="relative overflow-hidden" onclick="showDetails(${item.id}, '${item.media_type}')"> <div class="relative overflow-hidden" onclick="showDetails(${item.id}, '${item.media_type}')">
<img src="${poster}" class="w-full h-auto"> <img src="${poster}" class="w-full h-auto">
<div class="absolute inset-0 bg-black/60 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"> <div class="absolute inset-0 bg-black/60 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<span class="bg-white text-black px-4 py-2 rounded-full font-bold text-xs uppercase tracking-wider">Voir la fiche</span> <span class="bg-white text-black px-4 py-2 rounded-full font-bold text-xs uppercase tracking-wider">Voir la fiche</span>
</div> </div>
</div> </div>
<div class="p-3"> <div class="p-3">
<h3 class="font-bold text-sm truncate">${title}</h3> <h3 class="font-bold text-sm truncate">${title}</h3>
<div class="flex justify-between items-center mt-1"> <div class="flex justify-between items-center mt-1">
<span class="text-[10px] text-gray-400 uppercase">${type}</span> <span class="text-[10px] text-gray-400 uppercase">${type}</span>
<span class="text-[10px] text-blue-400 font-bold">${year}</span> <span class="text-[10px] text-blue-400 font-bold">${year}</span>
</div> </div>
</div> </div>
`; `;
grid.appendChild(card); grid.appendChild(card);
}); });
} else { } else {
grid.innerHTML = '<p class="col-span-full text-center py-10 text-gray-500">Aucun résultat trouvé pour cette recherche.</p>'; grid.innerHTML = '<p class="col-span-full text-center py-10 text-gray-500">Aucun résultat trouvé pour cette recherche.</p>';
} }
} }
// --- FICHE DÉTAILLÉE (MODAL) --- // --- FICHE DÉTAILLÉE (MODAL) ---
/** /**
* Récupère les détails complets d'un film et ouvre la modal * Récupère les détails complets d'un film et ouvre la modal
*/ */
async function showDetails(id, type) { async function showDetails(id, type) {
// TMDB utilise 'movie' ou 'tv', on s'assure d'avoir le bon type // TMDB utilise 'movie' ou 'tv', on s'assure d'avoir le bon type
const mediaType = (type === 'serie') ? 'tv' : type; const mediaType = (type === 'serie') ? 'tv' : type;
const movie = await apiRequest('getMovieDetails', { id: id, type: mediaType }); const movie = await apiRequest('getMovieDetails', { id: id, type: mediaType });
if (!movie) return; if (!movie) return;
const modal = document.getElementById('movieModal'); const modal = document.getElementById('movieModal');
// 1. Textes de base // 1. Textes de base
document.getElementById('modalTitle').innerText = movie.title || movie.name; document.getElementById('modalTitle').innerText = movie.title || movie.name;
const year = (movie.release_date || movie.first_air_date || "").substring(0, 4); const year = (movie.release_date || movie.first_air_date || "").substring(0, 4);
const duration = movie.runtime ? `${movie.runtime} min` : (movie.number_of_seasons ? `${movie.number_of_seasons} Saison(s)` : ""); const duration = movie.runtime ? `${movie.runtime} min` : (movie.number_of_seasons ? `${movie.number_of_seasons} Saison(s)` : "");
const genres = movie.genres.map(g => g.name).join(', '); const genres = movie.genres.map(g => g.name).join(', ');
document.getElementById('modalMeta').innerText = `${year}${genres} ${duration ? '• ' + duration : ''}`; document.getElementById('modalMeta').innerText = `${year}${genres} ${duration ? '• ' + duration : ''}`;
document.getElementById('modalOverview').innerText = movie.overview || "Aucun synopsis disponible pour le moment."; document.getElementById('modalOverview').innerText = movie.overview || "Aucun synopsis disponible pour le moment.";
document.getElementById('modalVote').innerText = movie.vote_average ? movie.vote_average.toFixed(1) : "N/A"; document.getElementById('modalVote').innerText = movie.vote_average ? movie.vote_average.toFixed(1) : "N/A";
// 2. Images (Bannière) // 2. Images (Bannière)
const bannerUrl = movie.backdrop_path ? `https://image.tmdb.org/t/p/original${movie.backdrop_path}` : ''; const bannerUrl = movie.backdrop_path ? `https://image.tmdb.org/t/p/original${movie.backdrop_path}` : '';
document.getElementById('modalBanner').style.backgroundImage = `url(${bannerUrl})`; document.getElementById('modalBanner').style.backgroundImage = `url(${bannerUrl})`;
// 3. Réalisateur // 3. Réalisateur
const director = movie.credits.crew.find(person => person.job === 'Director'); const director = movie.credits.crew.find(person => person.job === 'Director');
document.getElementById('modalDirector').innerText = director ? director.name : "Non renseigné"; document.getElementById('modalDirector').innerText = director ? director.name : "Non renseigné";
// 4. Casting (Les 8 premiers) // 4. Casting (Les 8 premiers)
const castContainer = document.getElementById('modalCast'); const castContainer = document.getElementById('modalCast');
castContainer.innerHTML = movie.credits.cast.slice(0, 8).map(actor => ` castContainer.innerHTML = movie.credits.cast.slice(0, 8).map(actor => `
<div class="min-w-[110px] text-center flex-shrink-0"> <div class="min-w-[110px] text-center flex-shrink-0">
<img src="${actor.profile_path ? 'https://image.tmdb.org/t/p/w185' + actor.profile_path : 'https://via.placeholder.com/185x278?text=No+Image'}" <img src="${actor.profile_path ? 'https://image.tmdb.org/t/p/w185' + actor.profile_path : 'https://via.placeholder.com/185x278?text=No+Image'}"
class="w-20 h-20 object-cover rounded-full mx-auto mb-2 border-2 border-slate-700 shadow-lg"> class="w-20 h-20 object-cover rounded-full mx-auto mb-2 border-2 border-slate-700 shadow-lg">
<p class="text-[10px] font-bold leading-tight text-white">${actor.name}</p> <p class="text-[10px] font-bold leading-tight text-white">${actor.name}</p>
<p class="text-[9px] text-gray-500 italic">${actor.character}</p> <p class="text-[9px] text-gray-500 italic">${actor.character}</p>
</div> </div>
`).join(''); `).join('');
// 5. Bouton Ajouter (on passe les infos pour la BDD) // 5. Bouton Ajouter (on passe les infos pour la BDD)
const simplifiedType = (mediaType === 'tv') ? 'serie' : 'film'; const simplifiedType = (mediaType === 'tv') ? 'serie' : 'film';
const btn = document.getElementById('modalMainBtn'); const btn = document.getElementById('modalMainBtn');
btn.innerText = "+ AJOUTER À LA LISTE"; btn.innerText = "+ AJOUTER À LA LISTE";
btn.className = "w-full bg-blue-600 hover:bg-blue-500 text-white py-4 rounded-xl font-black shadow-lg transition-all active:scale-95"; btn.className = "w-full bg-blue-600 hover:bg-blue-500 text-white py-4 rounded-xl font-black shadow-lg transition-all active:scale-95";
btn.onclick = () => addMovie(movie.id, movie.title || movie.name, movie.poster_path, simplifiedType); btn.onclick = () => addMovie(movie.id, movie.title || movie.name, movie.poster_path, simplifiedType);
// Cache les étoiles en mode recherche // Cache les étoiles en mode recherche
document.getElementById('ratingZone').classList.add('hidden'); document.getElementById('ratingZone').classList.add('hidden');
// Affichage // Affichage
modal.classList.remove('hidden'); modal.classList.remove('hidden');
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
} }
/** /**
* Ferme la modal * Ferme la modal
*/ */
function closeModal() { function closeModal() {
document.getElementById('movieModal').classList.add('hidden'); document.getElementById('movieModal').classList.add('hidden');
document.body.style.overflow = 'auto'; document.body.style.overflow = 'auto';
} }
// --- ACTIONS BDD --- // --- ACTIONS BDD ---
/** /**
* Ajoute un film dans la base de données MySQL * Ajoute un film dans la base de données MySQL
*/ */
async function addMovie(tmdbId, title, posterPath, type) { async function addMovie(tmdbId, title, posterPath, type) {
const result = await apiRequest('addMovie', { const result = await apiRequest('addMovie', {
tmdb_id: tmdbId, tmdb_id: tmdbId,
titre: title, titre: title,
affiche_path: posterPath, affiche_path: posterPath,
type: type type: type
}); });
if (result.success) { if (result.success) {
alert(`"${title}" a été ajouté à votre liste ! 🍿`); alert(`"${title}" a été ajouté à votre liste ! 🍿`);
closeModal(); closeModal();
} else { } else {
alert('Erreur lors de l\'ajout. Vérifie si le film n\'est pas déjà présent.'); alert('Erreur lors de l\'ajout. Vérifie si le film n\'est pas déjà présent.');
} }
} }
// Permettre la recherche avec la touche "Entrée" // Permettre la recherche avec la touche "Entrée"
document.getElementById('searchInput').addEventListener('keypress', function (e) { document.getElementById('searchInput').addEventListener('keypress', function (e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
performSearch(); performSearch();
} }
}); });
async function LogOut() { async function LogOut() {
const res = await fetch('../RequestHandler.php', { const res = await fetch('../RequestHandler.php', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'logout' }) body: JSON.stringify({ action: 'logout' })
}); });
const data = await res.json(); const data = await res.json();
if (data.success) { if (data.success) {
window.location.href = '../index.php'; window.location.href = '../index.php';
} }
} }
+81 -81
View File
@@ -1,82 +1,82 @@
<?php <?php
session_start(); session_start();
if (!isset($_SESSION['user_id'])) { if (!isset($_SESSION['user_id'])) {
header('Location: ../index.php'); // Redirige vers le login si pas de session header('Location: ../index.php'); // Redirige vers le login si pas de session
exit; exit;
} }
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Ma Liste - WatchGether</title> <title>Ma Liste - WatchGether</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
</head> </head>
<body class="bg-slate-900 text-white p-8"> <body class="bg-slate-900 text-white p-8">
<div class="max-w-6xl mx-auto"> <div class="max-w-6xl mx-auto">
<div class="flex justify-between items-center mb-10"> <div class="flex justify-between items-center mb-10">
<h1 class="text-3xl font-bold text-blue-400">Ma Liste 🍿</h1> <h1 class="text-3xl font-bold text-blue-400">Ma Liste 🍿</h1>
<a href="../search/index.php" class="bg-slate-800 px-4 py-2 rounded-lg hover:bg-slate-700 transition">← Retour à la recherche</a> <a href="../search/index.php" class="bg-slate-800 px-4 py-2 rounded-lg hover:bg-slate-700 transition">← Retour à la recherche</a>
</div> </div>
<div class="flex items-end mb-0 border-b border-slate-700"> <div class="flex items-end mb-0 border-b border-slate-700">
<button onclick="switchTab('my')" id="tab-my" <button onclick="switchTab('my')" id="tab-my"
class="px-8 py-4 bg-slate-800 text-white font-bold text-sm uppercase tracking-widest border-b-4 border-blue-500 transition-all"> class="px-8 py-4 bg-slate-800 text-white font-bold text-sm uppercase tracking-widest border-b-4 border-blue-500 transition-all">
Ma Liste Ma Liste
</button> </button>
<button onclick="switchTab('common')" id="tab-common" <button onclick="switchTab('common')" id="tab-common"
class="px-8 py-4 bg-slate-700/50 text-gray-400 font-bold text-sm uppercase tracking-widest border-b-4 border-transparent hover:bg-slate-800 hover:text-white transition-all"> class="px-8 py-4 bg-slate-700/50 text-gray-400 font-bold text-sm uppercase tracking-widest border-b-4 border-transparent hover:bg-slate-800 hover:text-white transition-all">
Notre Liste Notre Liste
</button> </button>
<button onclick="switchTab('partner')" id="tab-partner" <button onclick="switchTab('partner')" id="tab-partner"
class="px-8 py-4 bg-slate-700/50 text-gray-400 font-bold text-sm uppercase tracking-widest border-b-4 border-transparent hover:bg-slate-800 hover:text-white transition-all"> class="px-8 py-4 bg-slate-700/50 text-gray-400 font-bold text-sm uppercase tracking-widest border-b-4 border-transparent hover:bg-slate-800 hover:text-white transition-all">
Sa Liste Sa Liste
</button> </button>
</div> </div>
<div class="bg-slate-800 p-4 rounded-xl mb-8 flex flex-wrap gap-4 items-center"> <div class="bg-slate-800 p-4 rounded-xl mb-8 flex flex-wrap gap-4 items-center">
<div class="flex flex-col"> <div class="flex flex-col">
<label class="text-xs text-gray-400 mb-1 uppercase font-bold">Trier par</label> <label class="text-xs text-gray-400 mb-1 uppercase font-bold">Trier par</label>
<select id="sortOrder" onchange="renderList()" class="bg-slate-700 border-none rounded p-2 focus:ring-2 focus:ring-blue-500"> <select id="sortOrder" onchange="renderList()" class="bg-slate-700 border-none rounded p-2 focus:ring-2 focus:ring-blue-500">
<option value="date_ajout_desc">Date d'ajout (Récent)</option> <option value="date_ajout_desc">Date d'ajout (Récent)</option>
<option value="date_ajout_asc">Date d'ajout (Ancien)</option> <option value="date_ajout_asc">Date d'ajout (Ancien)</option>
<option value="titre_asc">Ordre Alphabétique</option> <option value="titre_asc">Ordre Alphabétique</option>
</select> </select>
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<label class="text-xs text-gray-400 mb-1 uppercase font-bold">Type</label> <label class="text-xs text-gray-400 mb-1 uppercase font-bold">Type</label>
<select id="filterType" onchange="renderList()" class="bg-slate-700 border-none rounded p-2"> <select id="filterType" onchange="renderList()" class="bg-slate-700 border-none rounded p-2">
<option value="all">Tout voir</option> <option value="all">Tout voir</option>
<option value="film">Films uniquement</option> <option value="film">Films uniquement</option>
<option value="serie">Séries uniquement</option> <option value="serie">Séries uniquement</option>
</select> </select>
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<label class="text-xs text-gray-400 mb-1 uppercase font-bold">Statut</label> <label class="text-xs text-gray-400 mb-1 uppercase font-bold">Statut</label>
<select id="filterStatus" onchange="renderList()" class="bg-slate-700 border-none rounded p-2"> <select id="filterStatus" onchange="renderList()" class="bg-slate-700 border-none rounded p-2">
<option value="all">Tout</option> <option value="all">Tout</option>
<option value="0">À voir</option> <option value="0">À voir</option>
<option value="1">Déjà vus</option> <option value="1">Déjà vus</option>
</select> </select>
</div> </div>
<div class="ml-auto text-sm text-gray-400"> <div class="ml-auto text-sm text-gray-400">
<span id="movieCount">0</span> éléments <span id="movieCount">0</span> éléments
</div> </div>
</div> </div>
<div id="myListGrid" class="grid grid-cols-2 md:grid-cols-5 gap-6"> <div id="myListGrid" class="grid grid-cols-2 md:grid-cols-5 gap-6">
</div> </div>
</div> </div>
<?php include '../modal-template.php';?> <?php include '../modal-template.php';?>
<script src="../js/list.js"></script> <script src="../js/list.js"></script>
</body> </body>
</html> </html>
+59 -59
View File
@@ -1,60 +1,60 @@
<div id="movieModal" class="fixed inset-0 bg-black/95 z-50 hidden overflow-y-auto"> <div id="movieModal" class="fixed inset-0 bg-black/95 z-50 hidden overflow-y-auto">
<div class="relative w-full max-w-5xl mx-auto min-h-screen bg-slate-900 shadow-2xl border-x border-white/10"> <div class="relative w-full max-w-5xl mx-auto min-h-screen bg-slate-900 shadow-2xl border-x border-white/10">
<button onclick="closeModal()" class="absolute top-6 right-6 z-50 bg-black/50 text-white p-2 rounded-full hover:bg-red-600 transition"> <button onclick="closeModal()" class="absolute top-6 right-6 z-50 bg-black/50 text-white p-2 rounded-full hover:bg-red-600 transition">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
</button> </button>
<div id="modalBanner" class="w-full h-[300px] md:h-[450px] bg-cover bg-center relative"> <div id="modalBanner" class="w-full h-[300px] md:h-[450px] bg-cover bg-center relative">
<div class="absolute inset-0 bg-gradient-to-t from-slate-900 via-slate-900/20 to-transparent"></div> <div class="absolute inset-0 bg-gradient-to-t from-slate-900 via-slate-900/20 to-transparent"></div>
<div class="absolute bottom-10 left-6 md:left-10 right-6"> <div class="absolute bottom-10 left-6 md:left-10 right-6">
<h2 id="modalTitle" class="text-4xl md:text-6xl font-black text-white drop-shadow-lg"></h2> <h2 id="modalTitle" class="text-4xl md:text-6xl font-black text-white drop-shadow-lg"></h2>
<p id="modalMeta" class="text-lg text-blue-400 font-semibold mt-2"></p> <p id="modalMeta" class="text-lg text-blue-400 font-semibold mt-2"></p>
</div> </div>
</div> </div>
<div class="p-6 md:p-10 grid md:grid-cols-3 gap-10"> <div class="p-6 md:p-10 grid md:grid-cols-3 gap-10">
<div class="md:col-span-2"> <div class="md:col-span-2">
<h3 class="text-xl font-bold mb-3 text-white border-b border-white/10 pb-2">Synopsis</h3> <h3 class="text-xl font-bold mb-3 text-white border-b border-white/10 pb-2">Synopsis</h3>
<p id="modalOverview" class="text-gray-300 leading-relaxed text-lg mb-8"></p> <p id="modalOverview" class="text-gray-300 leading-relaxed text-lg mb-8"></p>
<h3 class="text-xl font-bold mb-4 text-white border-b border-white/10 pb-2">Casting principal</h3> <h3 class="text-xl font-bold mb-4 text-white border-b border-white/10 pb-2">Casting principal</h3>
<div id="modalCast" class="flex gap-4 overflow-x-auto pb-6 scrollbar-hide"></div> <div id="modalCast" class="flex gap-4 overflow-x-auto pb-6 scrollbar-hide"></div>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div class="bg-slate-800/80 p-6 rounded-2xl border border-white/5"> <div class="bg-slate-800/80 p-6 rounded-2xl border border-white/5">
<div class="mb-4"> <div class="mb-4">
<span class="text-gray-400 text-xs uppercase tracking-widest block mb-1">Note TMDB</span> <span class="text-gray-400 text-xs uppercase tracking-widest block mb-1">Note TMDB</span>
<div class="flex items-baseline gap-1"> <div class="flex items-baseline gap-1">
<span id="modalVote" class="text-4xl font-black text-yellow-500"></span> <span id="modalVote" class="text-4xl font-black text-yellow-500"></span>
<span class="text-gray-500 font-bold">/ 10</span> <span class="text-gray-500 font-bold">/ 10</span>
</div> </div>
</div> </div>
<div class="mb-6"> <div class="mb-6">
<span class="text-gray-400 text-xs uppercase tracking-widest block mb-1">Réalisateur</span> <span class="text-gray-400 text-xs uppercase tracking-widest block mb-1">Réalisateur</span>
<span id="modalDirector" class="text-xl font-bold text-white"></span> <span id="modalDirector" class="text-xl font-bold text-white"></span>
</div> </div>
<div id="ratingZone" class="mb-6 hidden"> <div id="ratingZone" class="mb-6 hidden">
<span class="text-gray-400 text-xs uppercase tracking-widest block mb-2">Ta note</span> <span class="text-gray-400 text-xs uppercase tracking-widest block mb-2">Ta note</span>
<div class="flex gap-2 text-2xl" id="starContainer"> <div class="flex gap-2 text-2xl" id="starContainer">
<span onclick="setRating(1)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600"></span> <span onclick="setRating(1)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600"></span>
<span onclick="setRating(2)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600"></span> <span onclick="setRating(2)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600"></span>
<span onclick="setRating(3)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600"></span> <span onclick="setRating(3)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600"></span>
<span onclick="setRating(4)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600"></span> <span onclick="setRating(4)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600"></span>
<span onclick="setRating(5)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600"></span> <span onclick="setRating(5)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600"></span>
</div> </div>
</div> </div>
<button id="modalMainBtn" class="w-full py-4 rounded-xl font-black text-lg shadow-lg transition-all active:scale-95 text-white bg-blue-600 hover:bg-blue-500"> <button id="modalMainBtn" class="w-full py-4 rounded-xl font-black text-lg shadow-lg transition-all active:scale-95 text-white bg-blue-600 hover:bg-blue-500">
ACTION ACTION
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
+44 -44
View File
@@ -1,45 +1,45 @@
<?php <?php
session_start(); session_start();
if (!isset($_SESSION['user_id'])) { if (!isset($_SESSION['user_id'])) {
header('Location: ../index.php'); // Redirige vers le login si pas de session header('Location: ../index.php'); // Redirige vers le login si pas de session
exit; exit;
} }
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>WatchGether - Recherche</title> <title>WatchGether - Recherche</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
</head> </head>
<body class="bg-slate-900 text-white p-8"> <body class="bg-slate-900 text-white p-8">
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<div class="flex justify-between items-center mb-8"> <div class="flex justify-between items-center mb-8">
<a href="../ma-liste" class="bg-slate-800 hover:bg-slate-700 text-white px-6 py-3 rounded-xl font-bold border border-white/5 transition-all active:scale-95 shadow-lg"> <a href="../ma-liste" class="bg-slate-800 hover:bg-slate-700 text-white px-6 py-3 rounded-xl font-bold border border-white/5 transition-all active:scale-95 shadow-lg">
🍿 Ma liste 🍿 Ma liste
</a> </a>
<h1 class="text-5xl md:text-6xl font-black tracking-tighter italic"> <h1 class="text-5xl md:text-6xl font-black tracking-tighter italic">
<span class="text-blue-500">WATCH</span><span class="text-white">GETHER</span> <span class="text-blue-500">WATCH</span><span class="text-white">GETHER</span>
</h1> </h1>
<button onclick="LogOut()" class="bg-red-600 hover:bg-red-500 text-white px-6 py-3 rounded-xl font-bold shadow-lg shadow-red-900/20 transition-all active:scale-95"> <button onclick="LogOut()" class="bg-red-600 hover:bg-red-500 text-white px-6 py-3 rounded-xl font-bold shadow-lg shadow-red-900/20 transition-all active:scale-95">
👤 Se déconnecter 👤 Se déconnecter
</button> </button>
</div> </div>
<div class="flex gap-2 mb-10"> <div class="flex gap-2 mb-10">
<input type="text" id="searchInput" placeholder="Chercher un film ou une série..." <input type="text" id="searchInput" placeholder="Chercher un film ou une série..."
class="flex-grow p-4 rounded-lg bg-slate-800 border border-slate-700 focus:outline-none focus:border-blue-500 text-white"> class="flex-grow p-4 rounded-lg bg-slate-800 border border-slate-700 focus:outline-none focus:border-blue-500 text-white">
<button onclick="performSearch()" class="bg-blue-600 px-8 py-4 rounded-lg font-bold hover:bg-blue-500 transition shadow-lg"> <button onclick="performSearch()" class="bg-blue-600 px-8 py-4 rounded-lg font-bold hover:bg-blue-500 transition shadow-lg">
Chercher Chercher
</button> </button>
</div> </div>
<div id="resultsGrid" class="grid grid-cols-2 md:grid-cols-4 gap-6"></div> <div id="resultsGrid" class="grid grid-cols-2 md:grid-cols-4 gap-6"></div>
</div> </div>
<?php include '../modal-template.php';?> <?php include '../modal-template.php';?>
<script src="../js/search.js"></script> <script src="../js/search.js"></script>
</body> </body>
</html> </html>