Compare commits

...

24 Commits

Author SHA1 Message Date
Whykioh f223974fd2 Add VOJ logo 2026-07-11 08:07:17 +02:00
Whykioh 12a14e3956 Add meta UTF8 2026-07-11 08:05:50 +02:00
Whykioh 00ac59722f PHP no needed 2026-07-11 08:04:22 +02:00
Whykioh 059fe0f13a sdfs 2026-07-11 04:39:20 +02:00
Whykioh f1473456b5 sfqsf 2026-07-11 04:38:56 +02:00
Whykioh f49aeccd09 dsfsd 2026-07-11 04:38:06 +02:00
Whykioh ac2fac2a86 Fix Project section 2026-07-11 04:37:31 +02:00
Whykioh 373ee1e971 Fix img ext 2026-07-11 04:31:50 +02:00
Whykioh 538aef63c7 Add Imgs 2026-07-11 04:31:06 +02:00
Whykioh 2a41b77a84 aasaS 2026-07-11 04:12:23 +02:00
Whykioh 078fc3937c Add CSS 2026-07-11 04:11:15 +02:00
Whykioh 34aa001f5f Fix links issues 2026-07-11 04:10:48 +02:00
Whykioh 21d51ac65b Add Project content 2026-07-11 04:08:00 +02:00
Whykioh 8650ec2a56 Fix again 2026-07-11 03:50:53 +02:00
Whykioh e58e799edd Fix Btn issues 2026-07-11 03:46:13 +02:00
Whykioh 5b647a47f7 Fix CSS cache 2026-07-11 03:43:24 +02:00
Whykioh 05f7f5c12b Fix YouTube Integration 2026-07-11 03:38:58 +02:00
Whykioh dc68d20a85 Restyle website 2026-07-11 03:19:42 +02:00
Whykioh c567e5edac Change Title 2026-07-11 02:17:38 +02:00
Whykioh fd11bcc9ce test 2026-04-27 19:05:32 +02:00
Whykioh 0248a8f784 Merge branch 'main' of https://git.viensonjette.fr/Whykioh/main-website 2026-04-27 09:48:57 +02:00
Whykioh f506fd4b32 Merge branch 'main' of https://github.com/whykorp/home-page 2026-04-27 09:41:58 +02:00
Whykioh 0109146731 test 2026-04-27 09:41:54 +02:00
Whykioh 5463a3220a aa 2026-04-27 08:47:35 +02:00
20 changed files with 1224 additions and 1006 deletions
+1
View File
@@ -0,0 +1 @@
AAAAAAAAAA
+179 -55
View File
@@ -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;
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 KiB

After

Width:  |  Height:  |  Size: 953 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

+25
View File
@@ -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

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

+115
View File
@@ -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>
-47
View File
@@ -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>
View File
+200 -200
View File
@@ -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;
}
+42 -42
View File
@@ -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>
+299 -299
View File
@@ -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();
+179 -179
View File
@@ -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';
}
}
+81 -81
View File
@@ -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>
+59 -59
View File
@@ -1,60 +1,60 @@
<div id="movieModal" class="fixed inset-0 bg-black/95 z-50 hidden overflow-y-auto">
<div 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>
+44 -44
View File
@@ -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>