Compare commits
24 Commits
f57f9fe1d5
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f223974fd2 | |||
| 12a14e3956 | |||
| 00ac59722f | |||
| 059fe0f13a | |||
| f1473456b5 | |||
| f49aeccd09 | |||
| ac2fac2a86 | |||
| 373ee1e971 | |||
| 538aef63c7 | |||
| 2a41b77a84 | |||
| 078fc3937c | |||
| 34aa001f5f | |||
| 21d51ac65b | |||
| 8650ec2a56 | |||
| e58e799edd | |||
| 5b647a47f7 | |||
| 05f7f5c12b | |||
| dc68d20a85 | |||
| c567e5edac | |||
| fd11bcc9ce | |||
| 0248a8f784 | |||
| f506fd4b32 | |||
| 0109146731 | |||
| 5463a3220a |
@@ -0,0 +1 @@
|
||||
AAAAAAAAAA
|
||||
@@ -1,82 +1,206 @@
|
||||
:root {
|
||||
--bg-color: #050b16;
|
||||
--deep-blue: #0a192f;
|
||||
--glass-light: rgba(255, 255, 255, 0.08);
|
||||
--glass-border: rgba(255, 255, 255, 0.25);
|
||||
--accent-yellow: #fcd53f;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans', sans-serif;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
.container {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
border: 2px solid #2ecccc;
|
||||
border-radius: 25px;
|
||||
background-color: var(--bg-color);
|
||||
background-image: radial-gradient(at 10% 10%, rgba(10, 25, 47, 0.8) 0px, transparent 50%), radial-gradient(at 90% 90%, rgba(62, 202, 248, 0.15) 0px, transparent 50%), linear-gradient(135deg, #050b16 0%, #1a3c6f 100%);
|
||||
color: #e6f1ff;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #444;
|
||||
}
|
||||
.nav a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border: 2px solid #2ecccc;
|
||||
|
||||
.container {
|
||||
background: rgba(10, 25, 47, 0.08);
|
||||
backdrop-filter: blur(20px) saturate(180%);
|
||||
border-radius: 30px;
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5);
|
||||
width: 80%;
|
||||
margin: 40px auto;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
height: 2px;
|
||||
width: 80%;
|
||||
margin: 40px auto;
|
||||
background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.2) 50%, transparent 100%);
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
hr::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 15px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.social-links, .projects {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.social-links a {
|
||||
background: var(--glass-light);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--glass-border);
|
||||
padding: 15px;
|
||||
border-radius: 20px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.social-links a:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.social-links img {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
/* --- Projects --- */
|
||||
.projects {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 25px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.project {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(15px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 25px;
|
||||
background-color: #333;
|
||||
transition: all 0.3s ease-in-out;
|
||||
padding: 25px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
.nav a:hover {
|
||||
background-color: #2ecccc;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
||||
.project-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.project-header img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 15px;
|
||||
object-fit: cover;
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.project-info h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* --- Etiquettes (Tags) --- */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 50px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.purple-tag {
|
||||
background: rgba(147, 51, 234, 0.2);
|
||||
color: #d8b4fe;
|
||||
}
|
||||
|
||||
.yellow-tag {
|
||||
background: rgba(234, 179, 8, 0.2);
|
||||
color:#fde047;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
margin: 20px auto;
|
||||
border: 2px solid #2ecccc;
|
||||
border-radius: 25px;
|
||||
overflow: hidden;
|
||||
padding-bottom: 56.25%;
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 25px;
|
||||
border: 1px solid var(--glass-border);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.video-container iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: left;
|
||||
}
|
||||
.logo {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
.nav a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
padding: 15px 35px;
|
||||
margin: 0 10px;
|
||||
border-radius: 50px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--glass-border);
|
||||
transition: 0.3s;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.nav a:hover {
|
||||
background-color: var(--accent-yellow);
|
||||
color: var(--deep-blue);
|
||||
border-color: var(--accent-yellow);
|
||||
box-shadow: 0 0 20px var(--accent-yellow);
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 128px;
|
||||
width: 128px;
|
||||
vertical-align: middle;
|
||||
background: var(--glass-light);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 25px;
|
||||
border: 1px solid var(--glass-border);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
font-size: 48px;
|
||||
line-height: 60px;
|
||||
font-size: 64px;
|
||||
line-height: 128px;
|
||||
vertical-align: middle;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
background: rgba(10, 25, 47, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 30px;
|
||||
border: 1px solid var(--glass-border);
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
border: 2px solid #2ecccc;
|
||||
border-radius: 25px;
|
||||
margin: 40px auto;
|
||||
padding: 20px;
|
||||
background-color: #444;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
img.flash {
|
||||
display: inline-block;
|
||||
width: 128px;
|
||||
transition: 0.4s;
|
||||
};
|
||||
|
||||
img.flash:hover {
|
||||
transition: 0.4s;
|
||||
width: 800px
|
||||
};
|
||||
text-align: center;
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 615 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 797 KiB After Width: | Height: | Size: 953 KiB |
|
After Width: | Height: | Size: 239 KiB |
@@ -0,0 +1,25 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" width="100%" height="100%">
|
||||
<!-- Fond Noir -->
|
||||
|
||||
<!-- Style pour la police large et grasse -->
|
||||
<style>
|
||||
.logo-text {
|
||||
font-family: 'Arial Black', 'Impact', sans-serif;
|
||||
font-weight: 900;
|
||||
letter-spacing: -2px;
|
||||
}
|
||||
.watch {
|
||||
fill: #FFFFFF;
|
||||
}
|
||||
.gether {
|
||||
fill: #60a5fa;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--
|
||||
x="50" et textLength="400" force les deux mots à faire
|
||||
exactement la même largeur (400px), alignés au millimètre.
|
||||
-->
|
||||
<text x="50" y="220" font-size="140" textLength="400" lengthAdjust="spacingAndGlyphs" class="logo-text watch">WATCH</text>
|
||||
<text x="50" y="370" font-size="140" textLength="400" lengthAdjust="spacingAndGlyphs" class="logo-text gether">GETHER</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 798 B |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap" rel="stylesheet">
|
||||
<link href="css/style.css?v=1.0.5" rel="stylesheet">
|
||||
<link rel="icon" href="img/logo.png">
|
||||
<title>Whykioh.fr - Mon Portefolio</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<a href="https://www.youtube.com/@whykioh" target="_blank">
|
||||
<img class="logo" src="img/logo.png" alt="Logo">
|
||||
</a>
|
||||
<h1 class="title">Whykioh.fr</h1>
|
||||
</header>
|
||||
<div class="nav">
|
||||
<a href="https://ko-fi.com/whykioh" target="_blank">Ko-Fi</a>
|
||||
<a href="https://watchgether.whykioh.fr" target="_blank">WatchGether</a>
|
||||
<a href="https://gamel0ver.whykioh.fr" target="_blank">GameL0ver</a>
|
||||
<a href="https://vod.whykioh.fr" target="_blank">Jellyfin</a>
|
||||
<a href="https://voj.whykioh.fr" target="_blank">Viens On Jette</a>
|
||||
<a href="https://git.whykioh.fr/" target="_blank">Mes Repos</a>
|
||||
</div>
|
||||
<p><br></p>
|
||||
<hr>
|
||||
<h1>Bienvenue sur Whykioh.fr !</h1>
|
||||
<p>Le centre de tous mes projets webs, applications, et autres.<br>N'hésitez pas à explorer ! Mais aussi à me soutenir via un avis, ou un don.</p>
|
||||
<hr>
|
||||
<h1>Mes Réseaux :</h1>
|
||||
<p>Retrouvez-moi sur mes réseaux sociaux, et n'hésitez pas à me suivre !</p>
|
||||
<div class="social-links">
|
||||
<a href="https://www.youtube.com/@whykioh" target="_blank"><img src="img/youtube.png" alt="YouTube"></a>
|
||||
<a href="https://www.instagram.com/whykioh/" target="_blank"><img src="img/instagram.webp" alt="Instagram"></a>
|
||||
<a href="https://discord.gg/DaCAdCktCv" target="_blank"><img src="img/discord.webp" alt="Discord"></a>
|
||||
<a href="https://ko-fi.com/whykioh" target="_blank"><img src="img/ko-fi.png" alt="Ko-fi"></a>
|
||||
</div>
|
||||
<hr>
|
||||
<h1>Mes Projets :</h1>
|
||||
<p>Voici quelques-uns de mes projets :</p>
|
||||
<div class="projects">
|
||||
|
||||
<div class="project">
|
||||
<a href="https://watchgether.whykioh.fr" target="_blank" style="text-decoration: none; color: inherit;">
|
||||
<div class="project-header">
|
||||
<img src="img/watchgether.svg" alt="WatchGether">
|
||||
<div class="project-info">
|
||||
<h2>WatchGether</h2>
|
||||
<span class="tag purple-tag">In Progress</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<p>
|
||||
WatchGether est une liste de film à regarder en duo, WatchGether analyse et compare les listes du duo pour trouver des films en commun et en rapport avec les thèmes et genre apprécié de chaque utilisateur. Pour l'instant WatchGether n'est utilisable que par moi, mais sera ouvert au public prochainement.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<a href="https://gamel0ver.whykioh.fr" target="_blank" style="text-decoration: none; color: inherit;">
|
||||
<div class="project-header">
|
||||
<img src="img/gamel0ver.png" alt="GameL0ver">
|
||||
<div class="project-info">
|
||||
<h2>GameL0ver</h2>
|
||||
<span class="tag yellow-tag">Coming Soon</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<p>
|
||||
GameL0ver est un site de rencontre pour les joueurs, il permet de trouver des personnes avec qui jouer, voire même peut être l'amour, à des jeux vidéo en fonction de leurs préférences et de leurs jeux favoris. GameL0ver permet de discuter publiquement et même en privé et intègre un système de match avec des Likes et des Pass. GameL0ver est actuellement en développement et sera bientôt disponible.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<a href="https://whykioh.fr/blindtube" target="_blank" style="text-decoration: none; color: inherit;">
|
||||
<div class="project-header">
|
||||
<img src="img/BlindTube.png" alt="BlindTube">
|
||||
<div class="project-info">
|
||||
<h2>BlindTube</h2>
|
||||
<span class="tag purple-tag">In Progress</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<p>
|
||||
BlindTube est une application de Blind Test pour YouTube Music, elle permet d'effectuer un blind test seul ou à plusieurs en partant de vos playlists YouTube Music. BlindTube est actuellement en développement et sera disponible.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<a href="https://voj.whykioh.fr" target="_blank" style="text-decoration: none; color: inherit;">
|
||||
<div class="project-header">
|
||||
<img src="img/viensonjette.png" alt="Viens On Jette">
|
||||
<div class="project-info">
|
||||
<h2>Viens On Jette</h2>
|
||||
<span class="tag green-tag">Disponible</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<p>
|
||||
Viens On Jette est un duo avec mon ami <a href="https://www.youtube.com/@naxo175_" target="_blank">Naxo</a> sur YouTube, nous faisons des vidéos de jeux vidéo, des courts-métrages et d'autres contenus. Regardez dès maintenant !
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h1>Voici la dernière vidéo de <a href="https://www.youtube.com/@whykioh">Whykioh</a> :</h1>
|
||||
<div class="video-container">
|
||||
<iframe width="100%" height="100%" src="https://www.youtube.com/embed/q1rJG0Ue3d0?si=maK0gmAaMX2IDL67" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
Copyright | Whykioh® 2021-2026 | CIBOT NOËL Noah | <a href="https://ko-fi.com/whykioh" target="_blank">Ko-fi</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,47 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap" rel="stylesheet">
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
<link rel="icon" href="img/logo.png">
|
||||
<title>love</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<a href="https://www.youtube.com/@whykioh" target="_blank">
|
||||
<img class="logo" src="img/logo.png" alt="Logo">
|
||||
</a>
|
||||
<h1 class="title">WhyKorp</h1>
|
||||
</header>
|
||||
<div class="nav">
|
||||
<a href="#">Accueil</a>
|
||||
<a href="infos/index.php">Infos</a>
|
||||
<a href="https://artists.magroove.com/en/lt/whykioh/" target="_blank">Link Tree</a>
|
||||
<a href="tools/index.html">Outils</a>
|
||||
<a href="ruty/index.php">Ruty</a>
|
||||
<a href="lolivator/index.php">LoLivator</a>
|
||||
<a href="jpc/index.html">JPC</a>
|
||||
<a href="archives/index.php">Archives</a>
|
||||
</div>
|
||||
<p><br></p>
|
||||
<hr>
|
||||
<h1>Bienvenue sur le site officiel de la WhyKorp !</h1>
|
||||
<p>Vous y trouverez tout sur Whykioh aka Noah, et la WhyKorp.<br>Attention ! Site en cours de développement !<br>Si un disfonctionnement est trouvé veuillez le signaler.</p>
|
||||
<hr>
|
||||
<h1>Flash Info !</h1>
|
||||
<div class="flash">
|
||||
<p class="flash">Noah a enfin terminé l'EP 4 Elements, <br>il sera disponible sur toutes les plateformes <br>le 25 Décembre 2023</p>
|
||||
<img class="flash" src="img/last cover.png">
|
||||
</div>
|
||||
<hr>
|
||||
<h1>Voici la dernière vidéo de <a href="https://www.youtube.com/@whykioh">Whykioh</a> :</h1>
|
||||
<div class="video-container">
|
||||
<iframe width="557,55" height="315" src="https://www.youtube.com/embed/-ZPjXjvdZFc?si=ldsNogAHy636GnzU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
Copyright | Whykorp® 2021-2024 | ajoute le nom du site après l'url et un /
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,201 +1,201 @@
|
||||
<?php
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// --- CONFIGURATION BDD ---
|
||||
$host = 'localhost';
|
||||
$db = 'watchgether';
|
||||
$user = 'root';
|
||||
$pass = ''; // Vide par défaut sur Wamp
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
$apiKey = '23af653f99d2e7ac884415805e7ca84c';
|
||||
|
||||
try {
|
||||
$pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8", $user, $pass, $options);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => 'Connexion BDD échouée']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- LECTURE DE L'INPUT ---
|
||||
$json = file_get_contents('php://input');
|
||||
$data = json_decode($json, true);
|
||||
|
||||
if (!$data || !isset($data['action'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Aucune action spécifiée']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = $data['action'];
|
||||
$params = $data['params'] ?? [];
|
||||
|
||||
// Simulation d'un utilisateur connecté (à remplacer par ton système de login plus tard)
|
||||
$current_user_id = $_SESSION['user_id'] ?? 1;
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// --- RECHERCHE TMDB ---
|
||||
case 'searchTMDB':
|
||||
$query = urlencode($params['query']);
|
||||
$url = "https://api.themoviedb.org/3/search/multi?api_key=$apiKey&language=fr-FR&query=$query";
|
||||
|
||||
$response = file_get_contents($url);
|
||||
if ($response) {
|
||||
echo $response; // On renvoie directement le JSON de TMDB au front
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Erreur API TMDB']);
|
||||
}
|
||||
exit;
|
||||
|
||||
// --- GESTION DES FILMS ---
|
||||
case 'addMovie':
|
||||
// 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->execute([$params['tmdb_id'], $current_user_id]);
|
||||
if ($stmt->fetch()) {
|
||||
echo json_encode(['success' => false, 'error' => 'Film déjà dans la liste']);
|
||||
exit;
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO movies (tmdb_id, titre, affiche_path, type, user_id, vu) VALUES (?, ?, ?, ?, ?, 0)");
|
||||
$success = $stmt->execute([
|
||||
$params['tmdb_id'],
|
||||
$params['titre'],
|
||||
$params['affiche_path'],
|
||||
$params['type'], // 'film' ou 'serie'
|
||||
$current_user_id
|
||||
]);
|
||||
echo json_encode(['success' => $success]);
|
||||
exit;
|
||||
}
|
||||
|
||||
case 'getMyList':
|
||||
// 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->execute([$current_user_id]);
|
||||
echo json_encode(['success' => true, 'movies' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
|
||||
case 'getCommonList':
|
||||
// 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)
|
||||
$partner_id = ($current_user_id == 1) ? 2 : 1;
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT m1.* FROM movies m1
|
||||
INNER JOIN movies m2 ON m1.tmdb_id = m2.tmdb_id
|
||||
WHERE m1.user_id = ? AND m2.user_id = ?
|
||||
");
|
||||
$stmt->execute([$current_user_id, $partner_id]);
|
||||
echo json_encode(['success' => true, 'common_movies' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
|
||||
case 'getPartnerList':
|
||||
$partner_id = ($current_user_id == 1) ? 2 : 1;
|
||||
$stmt = $pdo->prepare("SELECT * FROM movies WHERE user_id = ? ORDER BY date_ajout DESC");
|
||||
$stmt->execute([$partner_id]);
|
||||
echo json_encode(['success' => true, 'movies' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
|
||||
// --- ACTIONS SUR LE FILM ---
|
||||
case 'toggleViewed':
|
||||
// Alterne entre vu (1) et non vu (0)
|
||||
$stmt = $pdo->prepare("UPDATE movies SET vu = !vu WHERE id = ?");
|
||||
$success = $stmt->execute([(int)$params['movie_id']]);
|
||||
echo json_encode(['success' => $success]);
|
||||
exit;
|
||||
|
||||
case 'deleteMovie':
|
||||
$stmt = $pdo->prepare("DELETE FROM movies WHERE id = ? AND user_id = ?");
|
||||
$success = $stmt->execute([(int)$params['movie_id'], $current_user_id]);
|
||||
echo json_encode(['success' => $success]);
|
||||
exit;
|
||||
|
||||
// --- COMMENTAIRES ---
|
||||
case 'addComment':
|
||||
$stmt = $pdo->prepare("INSERT INTO commentaires (movie_id, user_id, contenu) VALUES (?, ?, ?)");
|
||||
$success = $stmt->execute([
|
||||
(int)$params['movie_id'],
|
||||
$current_user_id,
|
||||
$params['text']
|
||||
]);
|
||||
echo json_encode(['success' => $success]);
|
||||
exit;
|
||||
|
||||
case 'getComments':
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT c.*, u.pseudo FROM commentaires c
|
||||
JOIN users u ON c.user_id = u.id
|
||||
WHERE c.movie_id = ? ORDER BY c.date_ajout DESC
|
||||
");
|
||||
$stmt->execute([(int)$params['movie_id']]);
|
||||
echo json_encode(['success' => true, 'comments' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
|
||||
case 'getMovieDetails':
|
||||
$id = $params['id'];
|
||||
$type = $params['type']; // 'movie' ou 'tv'
|
||||
// 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";
|
||||
|
||||
$response = file_get_contents($url);
|
||||
echo $response;
|
||||
exit;
|
||||
|
||||
case 'setStarsRating':
|
||||
$stmt = $pdo->prepare("INSERT INTO movies (rating) VALUES (?) WHERE id = ?");
|
||||
$success = $stmt->execute([
|
||||
(int)$params['movie_id'],
|
||||
(int)$params['rating']
|
||||
]);
|
||||
echo json_encode(['success' => $success]);
|
||||
exit;
|
||||
|
||||
case 'getStarsRating':
|
||||
$stmt = $pdo->prepare("SELECT rating FROM movies WHERE id = ?");
|
||||
$stmt->execute([(int)$params['movie_id']]);
|
||||
$rating = $stmt->fetchColumn();
|
||||
echo json_encode(['success' => true, 'rating' => $rating]);
|
||||
exit;
|
||||
|
||||
case 'register':
|
||||
$pseudo = $params['pseudo'];
|
||||
$pass = password_hash($params['password'], PASSWORD_DEFAULT);
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO users (pseudo, password) VALUES (?, ?)");
|
||||
try {
|
||||
$stmt->execute([$pseudo, $pass]);
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => 'Pseudo déjà pris']);
|
||||
}
|
||||
exit;
|
||||
|
||||
case 'login':
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE pseudo = ?");
|
||||
$stmt->execute([$params['pseudo']]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($params['password'], $user['password'])) {
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['pseudo'] = $user['pseudo'];
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Identifiants incorrects']);
|
||||
}
|
||||
exit;
|
||||
|
||||
case 'logout':
|
||||
session_destroy();
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
|
||||
default:
|
||||
echo json_encode(['success' => false, 'error' => 'Action inconnue']);
|
||||
exit;
|
||||
<?php
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// --- CONFIGURATION BDD ---
|
||||
$host = 'localhost';
|
||||
$db = 'watchgether';
|
||||
$user = 'root';
|
||||
$pass = ''; // Vide par défaut sur Wamp
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
$apiKey = '23af653f99d2e7ac884415805e7ca84c';
|
||||
|
||||
try {
|
||||
$pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8", $user, $pass, $options);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => 'Connexion BDD échouée']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- LECTURE DE L'INPUT ---
|
||||
$json = file_get_contents('php://input');
|
||||
$data = json_decode($json, true);
|
||||
|
||||
if (!$data || !isset($data['action'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Aucune action spécifiée']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = $data['action'];
|
||||
$params = $data['params'] ?? [];
|
||||
|
||||
// Simulation d'un utilisateur connecté (à remplacer par ton système de login plus tard)
|
||||
$current_user_id = $_SESSION['user_id'] ?? 1;
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// --- RECHERCHE TMDB ---
|
||||
case 'searchTMDB':
|
||||
$query = urlencode($params['query']);
|
||||
$url = "https://api.themoviedb.org/3/search/multi?api_key=$apiKey&language=fr-FR&query=$query";
|
||||
|
||||
$response = file_get_contents($url);
|
||||
if ($response) {
|
||||
echo $response; // On renvoie directement le JSON de TMDB au front
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Erreur API TMDB']);
|
||||
}
|
||||
exit;
|
||||
|
||||
// --- GESTION DES FILMS ---
|
||||
case 'addMovie':
|
||||
// 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->execute([$params['tmdb_id'], $current_user_id]);
|
||||
if ($stmt->fetch()) {
|
||||
echo json_encode(['success' => false, 'error' => 'Film déjà dans la liste']);
|
||||
exit;
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO movies (tmdb_id, titre, affiche_path, type, user_id, vu) VALUES (?, ?, ?, ?, ?, 0)");
|
||||
$success = $stmt->execute([
|
||||
$params['tmdb_id'],
|
||||
$params['titre'],
|
||||
$params['affiche_path'],
|
||||
$params['type'], // 'film' ou 'serie'
|
||||
$current_user_id
|
||||
]);
|
||||
echo json_encode(['success' => $success]);
|
||||
exit;
|
||||
}
|
||||
|
||||
case 'getMyList':
|
||||
// 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->execute([$current_user_id]);
|
||||
echo json_encode(['success' => true, 'movies' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
|
||||
case 'getCommonList':
|
||||
// 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)
|
||||
$partner_id = ($current_user_id == 1) ? 2 : 1;
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT m1.* FROM movies m1
|
||||
INNER JOIN movies m2 ON m1.tmdb_id = m2.tmdb_id
|
||||
WHERE m1.user_id = ? AND m2.user_id = ?
|
||||
");
|
||||
$stmt->execute([$current_user_id, $partner_id]);
|
||||
echo json_encode(['success' => true, 'common_movies' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
|
||||
case 'getPartnerList':
|
||||
$partner_id = ($current_user_id == 1) ? 2 : 1;
|
||||
$stmt = $pdo->prepare("SELECT * FROM movies WHERE user_id = ? ORDER BY date_ajout DESC");
|
||||
$stmt->execute([$partner_id]);
|
||||
echo json_encode(['success' => true, 'movies' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
|
||||
// --- ACTIONS SUR LE FILM ---
|
||||
case 'toggleViewed':
|
||||
// Alterne entre vu (1) et non vu (0)
|
||||
$stmt = $pdo->prepare("UPDATE movies SET vu = !vu WHERE id = ?");
|
||||
$success = $stmt->execute([(int)$params['movie_id']]);
|
||||
echo json_encode(['success' => $success]);
|
||||
exit;
|
||||
|
||||
case 'deleteMovie':
|
||||
$stmt = $pdo->prepare("DELETE FROM movies WHERE id = ? AND user_id = ?");
|
||||
$success = $stmt->execute([(int)$params['movie_id'], $current_user_id]);
|
||||
echo json_encode(['success' => $success]);
|
||||
exit;
|
||||
|
||||
// --- COMMENTAIRES ---
|
||||
case 'addComment':
|
||||
$stmt = $pdo->prepare("INSERT INTO commentaires (movie_id, user_id, contenu) VALUES (?, ?, ?)");
|
||||
$success = $stmt->execute([
|
||||
(int)$params['movie_id'],
|
||||
$current_user_id,
|
||||
$params['text']
|
||||
]);
|
||||
echo json_encode(['success' => $success]);
|
||||
exit;
|
||||
|
||||
case 'getComments':
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT c.*, u.pseudo FROM commentaires c
|
||||
JOIN users u ON c.user_id = u.id
|
||||
WHERE c.movie_id = ? ORDER BY c.date_ajout DESC
|
||||
");
|
||||
$stmt->execute([(int)$params['movie_id']]);
|
||||
echo json_encode(['success' => true, 'comments' => $stmt->fetchAll()]);
|
||||
exit;
|
||||
|
||||
case 'getMovieDetails':
|
||||
$id = $params['id'];
|
||||
$type = $params['type']; // 'movie' ou 'tv'
|
||||
// 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";
|
||||
|
||||
$response = file_get_contents($url);
|
||||
echo $response;
|
||||
exit;
|
||||
|
||||
case 'setStarsRating':
|
||||
$stmt = $pdo->prepare("INSERT INTO movies (rating) VALUES (?) WHERE id = ?");
|
||||
$success = $stmt->execute([
|
||||
(int)$params['movie_id'],
|
||||
(int)$params['rating']
|
||||
]);
|
||||
echo json_encode(['success' => $success]);
|
||||
exit;
|
||||
|
||||
case 'getStarsRating':
|
||||
$stmt = $pdo->prepare("SELECT rating FROM movies WHERE id = ?");
|
||||
$stmt->execute([(int)$params['movie_id']]);
|
||||
$rating = $stmt->fetchColumn();
|
||||
echo json_encode(['success' => true, 'rating' => $rating]);
|
||||
exit;
|
||||
|
||||
case 'register':
|
||||
$pseudo = $params['pseudo'];
|
||||
$pass = password_hash($params['password'], PASSWORD_DEFAULT);
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO users (pseudo, password) VALUES (?, ?)");
|
||||
try {
|
||||
$stmt->execute([$pseudo, $pass]);
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => 'Pseudo déjà pris']);
|
||||
}
|
||||
exit;
|
||||
|
||||
case 'login':
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE pseudo = ?");
|
||||
$stmt->execute([$params['pseudo']]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($params['password'], $user['password'])) {
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['pseudo'] = $user['pseudo'];
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Identifiants incorrects']);
|
||||
}
|
||||
exit;
|
||||
|
||||
case 'logout':
|
||||
session_destroy();
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
|
||||
default:
|
||||
echo json_encode(['success' => false, 'error' => 'Action inconnue']);
|
||||
exit;
|
||||
}
|
||||
@@ -1,43 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WatchGether - Connexion</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<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">
|
||||
<h1 class="text-3xl font-black text-center mb-8 text-blue-400 italic">WATCHGETHER</h1>
|
||||
|
||||
<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="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('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>
|
||||
|
||||
<script>
|
||||
async function handleAuth(action) {
|
||||
const pseudo = document.getElementById('pseudo').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
const response = await fetch('RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action, params: { pseudo, password } })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
if (action === 'register') alert("Compte créé ! Connecte-toi maintenant.");
|
||||
else window.location.href = 'search/index.php';
|
||||
} else {
|
||||
alert(data.error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WatchGether - Connexion</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<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">
|
||||
<h1 class="text-3xl font-black text-center mb-8 text-blue-400 italic">WATCHGETHER</h1>
|
||||
|
||||
<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="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('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>
|
||||
|
||||
<script>
|
||||
async function handleAuth(action) {
|
||||
const pseudo = document.getElementById('pseudo').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
const response = await fetch('RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action, params: { pseudo, password } })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
if (action === 'register') alert("Compte créé ! Connecte-toi maintenant.");
|
||||
else window.location.href = 'search/index.php';
|
||||
} else {
|
||||
alert(data.error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,300 +1,300 @@
|
||||
let allMovies = []; // Stockage local de la liste entière
|
||||
|
||||
window.onload = () => {
|
||||
loadList();
|
||||
document.getElementById('sortOrder').addEventListener('change', renderList);
|
||||
document.getElementById('filterType').addEventListener('change', renderList);
|
||||
document.getElementById('filterStatus').addEventListener('change', renderList);
|
||||
}
|
||||
|
||||
async function apiRequest(action, params = {}) {
|
||||
try {
|
||||
const response = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ action, params })
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Erreur API:", error);
|
||||
return { success: false, error: "Erreur de connexion au serveur" };
|
||||
}
|
||||
}
|
||||
|
||||
async function addMovie(tmdbId, title, posterPath, type) {
|
||||
const result = await apiRequest('addMovie', {
|
||||
tmdb_id: tmdbId,
|
||||
titre: title,
|
||||
affiche_path: posterPath,
|
||||
type: type
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
alert(`"${title}" a été ajouté à votre liste ! 🍿`);
|
||||
closeModal();
|
||||
} else {
|
||||
alert('Erreur lors de l\'ajout. Vérifie si le film n\'est pas déjà présent.');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
const response = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'getMyList' })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
allMovies = data.movies;
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const grid = document.getElementById('myListGrid');
|
||||
const sort = document.getElementById('sortOrder').value;
|
||||
const type = document.getElementById('filterType').value;
|
||||
const status = document.getElementById('filterStatus').value;
|
||||
|
||||
// 1. Filtrage
|
||||
let filtered = allMovies.filter(m => {
|
||||
const matchType = (type === 'all' || m.type === type);
|
||||
const matchStatus = (status === 'all' || m.vu == status);
|
||||
return matchType && matchStatus;
|
||||
});
|
||||
|
||||
// 2. Tri
|
||||
filtered.sort((a, b) => {
|
||||
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_desc') return new Date(b.date_ajout) - new Date(a.date_ajout);
|
||||
});
|
||||
|
||||
// 3. Affichage
|
||||
grid.innerHTML = '';
|
||||
document.getElementById('movieCount').innerText = filtered.length;
|
||||
|
||||
filtered.forEach(m => {
|
||||
const card = document.createElement('div');
|
||||
// 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`;
|
||||
|
||||
if (currentView === 'partner') {
|
||||
card.innerHTML = `
|
||||
<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}"
|
||||
class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}">
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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">
|
||||
+ AJOUTER À LA LISTE
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
} else if (currentView === 'common') {
|
||||
|
||||
card.innerHTML = `
|
||||
<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}"
|
||||
class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}">
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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'}">
|
||||
${m.vu == 1 ? 'VU' : 'À VOIR'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
card.innerHTML = `
|
||||
<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}"
|
||||
class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}">
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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">
|
||||
Voir plus
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Actions rapides
|
||||
async function toggleVu(id) {
|
||||
const res = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'toggleViewed', params: { movie_id: id } })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
// On met à jour localement pour ne pas recharger toute l'API
|
||||
const movie = allMovies.find(m => m.id == id);
|
||||
movie.vu = movie.vu == 1 ? 0 : 1;
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteMovie(id) {
|
||||
if(!confirm("Supprimer ce film de ta liste ?")) return;
|
||||
const res = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'deleteMovie', params: { movie_id: id } })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
allMovies = allMovies.filter(m => m.id != id);
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
let currentView = 'my'; // 'my', 'common', ou 'partner'
|
||||
|
||||
async function switchTab(view) {
|
||||
currentView = view;
|
||||
|
||||
// Mise à jour visuelle des boutons
|
||||
const tabs = ['my', 'common', 'partner'];
|
||||
tabs.forEach(t => {
|
||||
const btn = document.getElementById(`tab-${t}`);
|
||||
if (t === view) {
|
||||
btn.classList.replace('bg-slate-700/50', 'bg-slate-800');
|
||||
btn.classList.replace('text-gray-400', 'text-white');
|
||||
btn.classList.replace('border-transparent', 'border-blue-500');
|
||||
} else {
|
||||
btn.classList.replace('bg-slate-800', 'bg-slate-700/50');
|
||||
btn.classList.replace('text-white', 'text-gray-400');
|
||||
btn.classList.replace('border-blue-500', 'border-transparent');
|
||||
}
|
||||
});
|
||||
|
||||
// On recharge les données selon la vue
|
||||
let action = 'getMyList';
|
||||
if (view === 'common') action = 'getCommonList';
|
||||
if (view === 'partner') action = 'getPartnerList'; // Il faudra créer cette action dans RequestHandler
|
||||
|
||||
const response = await apiRequest(action);
|
||||
if (response.success) {
|
||||
allMovies = response.movies || response.common_movies;
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
async function showDetails(tmdbId, type, localId = null, isVu = 0) {
|
||||
// 1. On récupère les détails complets via TMDB (comme dans search.js)
|
||||
const mediaType = (type === 'serie') ? 'tv' : 'movie';
|
||||
|
||||
// On appelle ton RequestHandler pour avoir les détails + acteurs
|
||||
const movie = await apiRequest('getMovieDetails', { id: tmdbId, type: mediaType });
|
||||
if (!movie) return;
|
||||
|
||||
// 2. On remplit la modal avec les infos fraîches
|
||||
document.getElementById('modalTitle').innerText = movie.title || movie.name;
|
||||
|
||||
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 genres = movie.genres.map(g => g.name).join(', ');
|
||||
document.getElementById('modalMeta').innerText = `${year} • ${genres} ${duration ? '• ' + duration : ''}`;
|
||||
|
||||
document.getElementById('modalOverview').innerText = movie.overview || "Aucun synopsis disponible.";
|
||||
document.getElementById('modalVote').innerText = movie.vote_average ? movie.vote_average.toFixed(1) : "N/A";
|
||||
|
||||
// Image de bannière
|
||||
const bannerUrl = movie.backdrop_path ? `https://image.tmdb.org/t/p/original${movie.backdrop_path}` : '';
|
||||
document.getElementById('modalBanner').style.backgroundImage = `url(${bannerUrl})`;
|
||||
|
||||
// Réalisateur
|
||||
const director = movie.credits.crew.find(person => person.job === 'Director');
|
||||
document.getElementById('modalDirector').innerText = director ? director.name : "Non renseigné";
|
||||
|
||||
// Casting
|
||||
const castContainer = document.getElementById('modalCast');
|
||||
castContainer.innerHTML = movie.credits.cast.slice(0, 8).map(actor => `
|
||||
<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'}"
|
||||
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>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// 3. LOGIQUE DU BOUTON (Vu / Pas Vu)
|
||||
const btn = document.getElementById('modalMainBtn');
|
||||
const ratingZone = document.getElementById('ratingZone');
|
||||
|
||||
if (localId) {
|
||||
ratingZone.classList.remove('hidden'); // On montre les étoiles
|
||||
|
||||
if (isVu == 1) {
|
||||
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";
|
||||
} else {
|
||||
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.onclick = async () => {
|
||||
await toggleVu(localId); // Ta fonction qui change le statut en BDD
|
||||
closeModal();
|
||||
};
|
||||
}
|
||||
|
||||
// 4. LOGIQYUE DES ÉTOILES
|
||||
// On récupère la note actuelle pour ce film (si elle existe)
|
||||
apiRequest('getStarsRating', { movie_id: localId }).then(res => {
|
||||
if (res.success) {
|
||||
const currentRating = res.rating || 0;
|
||||
setRating(currentRating); // On affiche la note actuelle
|
||||
}
|
||||
});
|
||||
|
||||
// Affichage final
|
||||
document.getElementById('movieModal').classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
// Fonction pour les étoiles
|
||||
function setRating(note) {
|
||||
const currentMovieId = document.getElementById('modalMainBtn').onclick.toString().match(/toggleVu\((\d+)\)/)[1]; // Extraction de l'ID du film
|
||||
const stars = document.querySelectorAll('#starContainer span');
|
||||
stars.forEach((star, index) => {
|
||||
if (index < note) {
|
||||
star.classList.replace('text-gray-600', 'text-yellow-400');
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('movieModal').classList.add('hidden');
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
let allMovies = []; // Stockage local de la liste entière
|
||||
|
||||
window.onload = () => {
|
||||
loadList();
|
||||
document.getElementById('sortOrder').addEventListener('change', renderList);
|
||||
document.getElementById('filterType').addEventListener('change', renderList);
|
||||
document.getElementById('filterStatus').addEventListener('change', renderList);
|
||||
}
|
||||
|
||||
async function apiRequest(action, params = {}) {
|
||||
try {
|
||||
const response = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ action, params })
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Erreur API:", error);
|
||||
return { success: false, error: "Erreur de connexion au serveur" };
|
||||
}
|
||||
}
|
||||
|
||||
async function addMovie(tmdbId, title, posterPath, type) {
|
||||
const result = await apiRequest('addMovie', {
|
||||
tmdb_id: tmdbId,
|
||||
titre: title,
|
||||
affiche_path: posterPath,
|
||||
type: type
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
alert(`"${title}" a été ajouté à votre liste ! 🍿`);
|
||||
closeModal();
|
||||
} else {
|
||||
alert('Erreur lors de l\'ajout. Vérifie si le film n\'est pas déjà présent.');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
const response = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'getMyList' })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
allMovies = data.movies;
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const grid = document.getElementById('myListGrid');
|
||||
const sort = document.getElementById('sortOrder').value;
|
||||
const type = document.getElementById('filterType').value;
|
||||
const status = document.getElementById('filterStatus').value;
|
||||
|
||||
// 1. Filtrage
|
||||
let filtered = allMovies.filter(m => {
|
||||
const matchType = (type === 'all' || m.type === type);
|
||||
const matchStatus = (status === 'all' || m.vu == status);
|
||||
return matchType && matchStatus;
|
||||
});
|
||||
|
||||
// 2. Tri
|
||||
filtered.sort((a, b) => {
|
||||
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_desc') return new Date(b.date_ajout) - new Date(a.date_ajout);
|
||||
});
|
||||
|
||||
// 3. Affichage
|
||||
grid.innerHTML = '';
|
||||
document.getElementById('movieCount').innerText = filtered.length;
|
||||
|
||||
filtered.forEach(m => {
|
||||
const card = document.createElement('div');
|
||||
// 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`;
|
||||
|
||||
if (currentView === 'partner') {
|
||||
card.innerHTML = `
|
||||
<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}"
|
||||
class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}">
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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">
|
||||
+ AJOUTER À LA LISTE
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
} else if (currentView === 'common') {
|
||||
|
||||
card.innerHTML = `
|
||||
<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}"
|
||||
class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}">
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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'}">
|
||||
${m.vu == 1 ? 'VU' : 'À VOIR'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
card.innerHTML = `
|
||||
<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}"
|
||||
class="w-full h-full object-cover transition-transform group-hover:scale-110 ${m.vu == 1 ? 'opacity-40 grayscale' : ''}">
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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">
|
||||
Voir plus
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Actions rapides
|
||||
async function toggleVu(id) {
|
||||
const res = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'toggleViewed', params: { movie_id: id } })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
// On met à jour localement pour ne pas recharger toute l'API
|
||||
const movie = allMovies.find(m => m.id == id);
|
||||
movie.vu = movie.vu == 1 ? 0 : 1;
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteMovie(id) {
|
||||
if(!confirm("Supprimer ce film de ta liste ?")) return;
|
||||
const res = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'deleteMovie', params: { movie_id: id } })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
allMovies = allMovies.filter(m => m.id != id);
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
let currentView = 'my'; // 'my', 'common', ou 'partner'
|
||||
|
||||
async function switchTab(view) {
|
||||
currentView = view;
|
||||
|
||||
// Mise à jour visuelle des boutons
|
||||
const tabs = ['my', 'common', 'partner'];
|
||||
tabs.forEach(t => {
|
||||
const btn = document.getElementById(`tab-${t}`);
|
||||
if (t === view) {
|
||||
btn.classList.replace('bg-slate-700/50', 'bg-slate-800');
|
||||
btn.classList.replace('text-gray-400', 'text-white');
|
||||
btn.classList.replace('border-transparent', 'border-blue-500');
|
||||
} else {
|
||||
btn.classList.replace('bg-slate-800', 'bg-slate-700/50');
|
||||
btn.classList.replace('text-white', 'text-gray-400');
|
||||
btn.classList.replace('border-blue-500', 'border-transparent');
|
||||
}
|
||||
});
|
||||
|
||||
// On recharge les données selon la vue
|
||||
let action = 'getMyList';
|
||||
if (view === 'common') action = 'getCommonList';
|
||||
if (view === 'partner') action = 'getPartnerList'; // Il faudra créer cette action dans RequestHandler
|
||||
|
||||
const response = await apiRequest(action);
|
||||
if (response.success) {
|
||||
allMovies = response.movies || response.common_movies;
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
async function showDetails(tmdbId, type, localId = null, isVu = 0) {
|
||||
// 1. On récupère les détails complets via TMDB (comme dans search.js)
|
||||
const mediaType = (type === 'serie') ? 'tv' : 'movie';
|
||||
|
||||
// On appelle ton RequestHandler pour avoir les détails + acteurs
|
||||
const movie = await apiRequest('getMovieDetails', { id: tmdbId, type: mediaType });
|
||||
if (!movie) return;
|
||||
|
||||
// 2. On remplit la modal avec les infos fraîches
|
||||
document.getElementById('modalTitle').innerText = movie.title || movie.name;
|
||||
|
||||
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 genres = movie.genres.map(g => g.name).join(', ');
|
||||
document.getElementById('modalMeta').innerText = `${year} • ${genres} ${duration ? '• ' + duration : ''}`;
|
||||
|
||||
document.getElementById('modalOverview').innerText = movie.overview || "Aucun synopsis disponible.";
|
||||
document.getElementById('modalVote').innerText = movie.vote_average ? movie.vote_average.toFixed(1) : "N/A";
|
||||
|
||||
// Image de bannière
|
||||
const bannerUrl = movie.backdrop_path ? `https://image.tmdb.org/t/p/original${movie.backdrop_path}` : '';
|
||||
document.getElementById('modalBanner').style.backgroundImage = `url(${bannerUrl})`;
|
||||
|
||||
// Réalisateur
|
||||
const director = movie.credits.crew.find(person => person.job === 'Director');
|
||||
document.getElementById('modalDirector').innerText = director ? director.name : "Non renseigné";
|
||||
|
||||
// Casting
|
||||
const castContainer = document.getElementById('modalCast');
|
||||
castContainer.innerHTML = movie.credits.cast.slice(0, 8).map(actor => `
|
||||
<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'}"
|
||||
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>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// 3. LOGIQUE DU BOUTON (Vu / Pas Vu)
|
||||
const btn = document.getElementById('modalMainBtn');
|
||||
const ratingZone = document.getElementById('ratingZone');
|
||||
|
||||
if (localId) {
|
||||
ratingZone.classList.remove('hidden'); // On montre les étoiles
|
||||
|
||||
if (isVu == 1) {
|
||||
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";
|
||||
} else {
|
||||
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.onclick = async () => {
|
||||
await toggleVu(localId); // Ta fonction qui change le statut en BDD
|
||||
closeModal();
|
||||
};
|
||||
}
|
||||
|
||||
// 4. LOGIQYUE DES ÉTOILES
|
||||
// On récupère la note actuelle pour ce film (si elle existe)
|
||||
apiRequest('getStarsRating', { movie_id: localId }).then(res => {
|
||||
if (res.success) {
|
||||
const currentRating = res.rating || 0;
|
||||
setRating(currentRating); // On affiche la note actuelle
|
||||
}
|
||||
});
|
||||
|
||||
// Affichage final
|
||||
document.getElementById('movieModal').classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
// Fonction pour les étoiles
|
||||
function setRating(note) {
|
||||
const currentMovieId = document.getElementById('modalMainBtn').onclick.toString().match(/toggleVu\((\d+)\)/)[1]; // Extraction de l'ID du film
|
||||
const stars = document.querySelectorAll('#starContainer span');
|
||||
stars.forEach((star, index) => {
|
||||
if (index < note) {
|
||||
star.classList.replace('text-gray-600', 'text-yellow-400');
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('movieModal').classList.add('hidden');
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
loadList();
|
||||
@@ -1,180 +1,180 @@
|
||||
// --- CONFIGURATION & UTILITAIRES ---
|
||||
|
||||
/**
|
||||
* Fonction universelle pour parler à ton RequestHandler
|
||||
* @param {string} action - L'action à exécuter (ex: 'searchTMDB')
|
||||
* @param {object} params - Les données à envoyer
|
||||
*/
|
||||
async function apiRequest(action, params = {}) {
|
||||
try {
|
||||
const response = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ action, params })
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Erreur API:", error);
|
||||
return { success: false, error: "Erreur de connexion au serveur" };
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('ratingZone').classList.add('hidden');
|
||||
|
||||
// --- RECHERCHE ---
|
||||
|
||||
/**
|
||||
* Lance la recherche TMDB et affiche les résultats sous forme de cartes
|
||||
*/
|
||||
async function performSearch() {
|
||||
const query = document.getElementById('searchInput').value;
|
||||
if (!query) return;
|
||||
|
||||
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>';
|
||||
|
||||
const data = await apiRequest('searchTMDB', { query: query });
|
||||
|
||||
grid.innerHTML = ''; // On vide
|
||||
|
||||
if (data.results && data.results.length > 0) {
|
||||
data.results.forEach(item => {
|
||||
// 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;
|
||||
|
||||
const title = item.title || item.name;
|
||||
const poster = `https://image.tmdb.org/t/p/w500${item.poster_path}`;
|
||||
const type = item.media_type === 'tv' ? 'serie' : 'film';
|
||||
const year = (item.release_date || item.first_air_date || "").substring(0, 4);
|
||||
|
||||
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.innerHTML = `
|
||||
<div class="relative overflow-hidden" onclick="showDetails(${item.id}, '${item.media_type}')">
|
||||
<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">
|
||||
<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 class="p-3">
|
||||
<h3 class="font-bold text-sm truncate">${title}</h3>
|
||||
<div class="flex justify-between items-center mt-1">
|
||||
<span class="text-[10px] text-gray-400 uppercase">${type}</span>
|
||||
<span class="text-[10px] text-blue-400 font-bold">${year}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
} else {
|
||||
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) ---
|
||||
|
||||
/**
|
||||
* Récupère les détails complets d'un film et ouvre la modal
|
||||
*/
|
||||
async function showDetails(id, type) {
|
||||
// TMDB utilise 'movie' ou 'tv', on s'assure d'avoir le bon type
|
||||
const mediaType = (type === 'serie') ? 'tv' : type;
|
||||
|
||||
const movie = await apiRequest('getMovieDetails', { id: id, type: mediaType });
|
||||
if (!movie) return;
|
||||
|
||||
const modal = document.getElementById('movieModal');
|
||||
|
||||
// 1. Textes de base
|
||||
document.getElementById('modalTitle').innerText = movie.title || movie.name;
|
||||
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 genres = movie.genres.map(g => g.name).join(', ');
|
||||
|
||||
document.getElementById('modalMeta').innerText = `${year} • ${genres} ${duration ? '• ' + duration : ''}`;
|
||||
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";
|
||||
|
||||
// 2. Images (Bannière)
|
||||
const bannerUrl = movie.backdrop_path ? `https://image.tmdb.org/t/p/original${movie.backdrop_path}` : '';
|
||||
document.getElementById('modalBanner').style.backgroundImage = `url(${bannerUrl})`;
|
||||
|
||||
// 3. Réalisateur
|
||||
const director = movie.credits.crew.find(person => person.job === 'Director');
|
||||
document.getElementById('modalDirector').innerText = director ? director.name : "Non renseigné";
|
||||
|
||||
// 4. Casting (Les 8 premiers)
|
||||
const castContainer = document.getElementById('modalCast');
|
||||
castContainer.innerHTML = movie.credits.cast.slice(0, 8).map(actor => `
|
||||
<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'}"
|
||||
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-[9px] text-gray-500 italic">${actor.character}</p>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// 5. Bouton Ajouter (on passe les infos pour la BDD)
|
||||
const simplifiedType = (mediaType === 'tv') ? 'serie' : 'film';
|
||||
const btn = document.getElementById('modalMainBtn');
|
||||
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.onclick = () => addMovie(movie.id, movie.title || movie.name, movie.poster_path, simplifiedType);
|
||||
|
||||
// Cache les étoiles en mode recherche
|
||||
document.getElementById('ratingZone').classList.add('hidden');
|
||||
|
||||
// Affichage
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* Ferme la modal
|
||||
*/
|
||||
function closeModal() {
|
||||
document.getElementById('movieModal').classList.add('hidden');
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
// --- ACTIONS BDD ---
|
||||
|
||||
/**
|
||||
* Ajoute un film dans la base de données MySQL
|
||||
*/
|
||||
async function addMovie(tmdbId, title, posterPath, type) {
|
||||
const result = await apiRequest('addMovie', {
|
||||
tmdb_id: tmdbId,
|
||||
titre: title,
|
||||
affiche_path: posterPath,
|
||||
type: type
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
alert(`"${title}" a été ajouté à votre liste ! 🍿`);
|
||||
closeModal();
|
||||
} else {
|
||||
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"
|
||||
document.getElementById('searchInput').addEventListener('keypress', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
performSearch();
|
||||
}
|
||||
});
|
||||
|
||||
async function LogOut() {
|
||||
const res = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'logout' })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
window.location.href = '../index.php';
|
||||
}
|
||||
// --- CONFIGURATION & UTILITAIRES ---
|
||||
|
||||
/**
|
||||
* Fonction universelle pour parler à ton RequestHandler
|
||||
* @param {string} action - L'action à exécuter (ex: 'searchTMDB')
|
||||
* @param {object} params - Les données à envoyer
|
||||
*/
|
||||
async function apiRequest(action, params = {}) {
|
||||
try {
|
||||
const response = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ action, params })
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Erreur API:", error);
|
||||
return { success: false, error: "Erreur de connexion au serveur" };
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('ratingZone').classList.add('hidden');
|
||||
|
||||
// --- RECHERCHE ---
|
||||
|
||||
/**
|
||||
* Lance la recherche TMDB et affiche les résultats sous forme de cartes
|
||||
*/
|
||||
async function performSearch() {
|
||||
const query = document.getElementById('searchInput').value;
|
||||
if (!query) return;
|
||||
|
||||
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>';
|
||||
|
||||
const data = await apiRequest('searchTMDB', { query: query });
|
||||
|
||||
grid.innerHTML = ''; // On vide
|
||||
|
||||
if (data.results && data.results.length > 0) {
|
||||
data.results.forEach(item => {
|
||||
// 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;
|
||||
|
||||
const title = item.title || item.name;
|
||||
const poster = `https://image.tmdb.org/t/p/w500${item.poster_path}`;
|
||||
const type = item.media_type === 'tv' ? 'serie' : 'film';
|
||||
const year = (item.release_date || item.first_air_date || "").substring(0, 4);
|
||||
|
||||
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.innerHTML = `
|
||||
<div class="relative overflow-hidden" onclick="showDetails(${item.id}, '${item.media_type}')">
|
||||
<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">
|
||||
<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 class="p-3">
|
||||
<h3 class="font-bold text-sm truncate">${title}</h3>
|
||||
<div class="flex justify-between items-center mt-1">
|
||||
<span class="text-[10px] text-gray-400 uppercase">${type}</span>
|
||||
<span class="text-[10px] text-blue-400 font-bold">${year}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
} else {
|
||||
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) ---
|
||||
|
||||
/**
|
||||
* Récupère les détails complets d'un film et ouvre la modal
|
||||
*/
|
||||
async function showDetails(id, type) {
|
||||
// TMDB utilise 'movie' ou 'tv', on s'assure d'avoir le bon type
|
||||
const mediaType = (type === 'serie') ? 'tv' : type;
|
||||
|
||||
const movie = await apiRequest('getMovieDetails', { id: id, type: mediaType });
|
||||
if (!movie) return;
|
||||
|
||||
const modal = document.getElementById('movieModal');
|
||||
|
||||
// 1. Textes de base
|
||||
document.getElementById('modalTitle').innerText = movie.title || movie.name;
|
||||
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 genres = movie.genres.map(g => g.name).join(', ');
|
||||
|
||||
document.getElementById('modalMeta').innerText = `${year} • ${genres} ${duration ? '• ' + duration : ''}`;
|
||||
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";
|
||||
|
||||
// 2. Images (Bannière)
|
||||
const bannerUrl = movie.backdrop_path ? `https://image.tmdb.org/t/p/original${movie.backdrop_path}` : '';
|
||||
document.getElementById('modalBanner').style.backgroundImage = `url(${bannerUrl})`;
|
||||
|
||||
// 3. Réalisateur
|
||||
const director = movie.credits.crew.find(person => person.job === 'Director');
|
||||
document.getElementById('modalDirector').innerText = director ? director.name : "Non renseigné";
|
||||
|
||||
// 4. Casting (Les 8 premiers)
|
||||
const castContainer = document.getElementById('modalCast');
|
||||
castContainer.innerHTML = movie.credits.cast.slice(0, 8).map(actor => `
|
||||
<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'}"
|
||||
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-[9px] text-gray-500 italic">${actor.character}</p>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// 5. Bouton Ajouter (on passe les infos pour la BDD)
|
||||
const simplifiedType = (mediaType === 'tv') ? 'serie' : 'film';
|
||||
const btn = document.getElementById('modalMainBtn');
|
||||
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.onclick = () => addMovie(movie.id, movie.title || movie.name, movie.poster_path, simplifiedType);
|
||||
|
||||
// Cache les étoiles en mode recherche
|
||||
document.getElementById('ratingZone').classList.add('hidden');
|
||||
|
||||
// Affichage
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* Ferme la modal
|
||||
*/
|
||||
function closeModal() {
|
||||
document.getElementById('movieModal').classList.add('hidden');
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
// --- ACTIONS BDD ---
|
||||
|
||||
/**
|
||||
* Ajoute un film dans la base de données MySQL
|
||||
*/
|
||||
async function addMovie(tmdbId, title, posterPath, type) {
|
||||
const result = await apiRequest('addMovie', {
|
||||
tmdb_id: tmdbId,
|
||||
titre: title,
|
||||
affiche_path: posterPath,
|
||||
type: type
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
alert(`"${title}" a été ajouté à votre liste ! 🍿`);
|
||||
closeModal();
|
||||
} else {
|
||||
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"
|
||||
document.getElementById('searchInput').addEventListener('keypress', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
performSearch();
|
||||
}
|
||||
});
|
||||
|
||||
async function LogOut() {
|
||||
const res = await fetch('../RequestHandler.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'logout' })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
window.location.href = '../index.php';
|
||||
}
|
||||
}
|
||||
@@ -1,82 +1,82 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: ../index.php'); // Redirige vers le login si pas de session
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ma Liste - WatchGether</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-white p-8">
|
||||
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="flex justify-between items-center mb-10">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="flex items-end mb-0 border-b border-slate-700">
|
||||
<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">
|
||||
Ma Liste
|
||||
</button>
|
||||
|
||||
<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">
|
||||
Notre Liste
|
||||
</button>
|
||||
|
||||
<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">
|
||||
Sa Liste
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-800 p-4 rounded-xl mb-8 flex flex-wrap gap-4 items-center">
|
||||
<div class="flex flex-col">
|
||||
<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">
|
||||
<option value="date_ajout_desc">Date d'ajout (Récent)</option>
|
||||
<option value="date_ajout_asc">Date d'ajout (Ancien)</option>
|
||||
<option value="titre_asc">Ordre Alphabétique</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<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">
|
||||
<option value="all">Tout voir</option>
|
||||
<option value="film">Films uniquement</option>
|
||||
<option value="serie">Séries uniquement</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<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">
|
||||
<option value="all">Tout</option>
|
||||
<option value="0">À voir</option>
|
||||
<option value="1">Déjà vus</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto text-sm text-gray-400">
|
||||
<span id="movieCount">0</span> éléments
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="myListGrid" class="grid grid-cols-2 md:grid-cols-5 gap-6">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include '../modal-template.php';?>
|
||||
|
||||
<script src="../js/list.js"></script>
|
||||
</body>
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: ../index.php'); // Redirige vers le login si pas de session
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ma Liste - WatchGether</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-white p-8">
|
||||
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="flex justify-between items-center mb-10">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="flex items-end mb-0 border-b border-slate-700">
|
||||
<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">
|
||||
Ma Liste
|
||||
</button>
|
||||
|
||||
<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">
|
||||
Notre Liste
|
||||
</button>
|
||||
|
||||
<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">
|
||||
Sa Liste
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-800 p-4 rounded-xl mb-8 flex flex-wrap gap-4 items-center">
|
||||
<div class="flex flex-col">
|
||||
<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">
|
||||
<option value="date_ajout_desc">Date d'ajout (Récent)</option>
|
||||
<option value="date_ajout_asc">Date d'ajout (Ancien)</option>
|
||||
<option value="titre_asc">Ordre Alphabétique</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<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">
|
||||
<option value="all">Tout voir</option>
|
||||
<option value="film">Films uniquement</option>
|
||||
<option value="serie">Séries uniquement</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<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">
|
||||
<option value="all">Tout</option>
|
||||
<option value="0">À voir</option>
|
||||
<option value="1">Déjà vus</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto text-sm text-gray-400">
|
||||
<span id="movieCount">0</span> éléments
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="myListGrid" class="grid grid-cols-2 md:grid-cols-5 gap-6">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include '../modal-template.php';?>
|
||||
|
||||
<script src="../js/list.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,60 +1,60 @@
|
||||
<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">
|
||||
|
||||
<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">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<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 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>
|
||||
<p id="modalMeta" class="text-lg text-blue-400 font-semibold mt-2"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 md:p-10 grid md:grid-cols-3 gap-10">
|
||||
<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>
|
||||
<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>
|
||||
<div id="modalCast" class="flex gap-4 overflow-x-auto pb-6 scrollbar-hide"></div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-slate-800/80 p-6 rounded-2xl border border-white/5">
|
||||
<div class="mb-4">
|
||||
<span class="text-gray-400 text-xs uppercase tracking-widest block mb-1">Note TMDB</span>
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span id="modalVote" class="text-4xl font-black text-yellow-500"></span>
|
||||
<span class="text-gray-500 font-bold">/ 10</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div id="ratingZone" class="mb-6 hidden">
|
||||
<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">
|
||||
<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(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(5)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600">★</span>
|
||||
</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">
|
||||
ACTION
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
|
||||
<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">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<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 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>
|
||||
<p id="modalMeta" class="text-lg text-blue-400 font-semibold mt-2"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 md:p-10 grid md:grid-cols-3 gap-10">
|
||||
<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>
|
||||
<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>
|
||||
<div id="modalCast" class="flex gap-4 overflow-x-auto pb-6 scrollbar-hide"></div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-slate-800/80 p-6 rounded-2xl border border-white/5">
|
||||
<div class="mb-4">
|
||||
<span class="text-gray-400 text-xs uppercase tracking-widest block mb-1">Note TMDB</span>
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span id="modalVote" class="text-4xl font-black text-yellow-500"></span>
|
||||
<span class="text-gray-500 font-bold">/ 10</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div id="ratingZone" class="mb-6 hidden">
|
||||
<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">
|
||||
<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(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(5)" class="cursor-pointer hover:text-yellow-400 transition text-gray-600">★</span>
|
||||
</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">
|
||||
ACTION
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,45 +1,45 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: ../index.php'); // Redirige vers le login si pas de session
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WatchGether - Recherche</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-white p-8">
|
||||
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<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">
|
||||
🍿 Ma liste
|
||||
</a>
|
||||
|
||||
<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>
|
||||
</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">
|
||||
👤 Se déconnecter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mb-10">
|
||||
<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">
|
||||
<button onclick="performSearch()" class="bg-blue-600 px-8 py-4 rounded-lg font-bold hover:bg-blue-500 transition shadow-lg">
|
||||
Chercher
|
||||
</button>
|
||||
</div>
|
||||
<div id="resultsGrid" class="grid grid-cols-2 md:grid-cols-4 gap-6"></div>
|
||||
</div>
|
||||
<?php include '../modal-template.php';?>
|
||||
<script src="../js/search.js"></script>
|
||||
</body>
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: ../index.php'); // Redirige vers le login si pas de session
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WatchGether - Recherche</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-white p-8">
|
||||
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<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">
|
||||
🍿 Ma liste
|
||||
</a>
|
||||
|
||||
<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>
|
||||
</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">
|
||||
👤 Se déconnecter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mb-10">
|
||||
<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">
|
||||
<button onclick="performSearch()" class="bg-blue-600 px-8 py-4 rounded-lg font-bold hover:bg-blue-500 transition shadow-lg">
|
||||
Chercher
|
||||
</button>
|
||||
</div>
|
||||
<div id="resultsGrid" class="grid grid-cols-2 md:grid-cols-4 gap-6"></div>
|
||||
</div>
|
||||
<?php include '../modal-template.php';?>
|
||||
<script src="../js/search.js"></script>
|
||||
</body>
|
||||
</html>
|
||||