diff --git a/admin.php b/admin.php new file mode 100644 index 0000000..1d84213 --- /dev/null +++ b/admin.php @@ -0,0 +1,59 @@ + + + + + + + + Admin Dashboard - StatTracker + + + + + + + + +
+
+ +
+ +
+
+

Admin Dashboard

+ +
+
+

User Management

+
+ +
+
+
+ + +
+ + + + + diff --git a/api/add_favorite.php b/api/add_favorite.php new file mode 100644 index 0000000..e887f07 --- /dev/null +++ b/api/add_favorite.php @@ -0,0 +1,58 @@ + 'error', 'message' => 'You must be logged in to add favorites.']); + exit; +} + +$userId = $_SESSION['user_id']; +$playerData = json_decode(file_get_contents('php://input'), true); + +if (!$playerData || !isset($playerData['idPlayer'])) { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Invalid player data.']); + exit; +} + +$playerId = $playerData['idPlayer']; + +try { + $pdo = db(); + $pdo->beginTransaction(); + + // 1. Insert/update player in the main players table + $stmt = $pdo->prepare( + 'INSERT INTO players (id, name, team, sport, image_url) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE name = VALUES(name);' + ); + $stmt->execute([ + $playerId, + $playerData['strPlayer'], + $playerData['strTeam'], + $playerData['strSport'], + $playerData['strCutout'] + ]); + + // 2. Add to user_favorites (ignore if already exists) + $stmt = $pdo->prepare('INSERT IGNORE INTO user_favorites (user_id, player_id) VALUES (?, ?)'); + $stmt->execute([$userId, $playerId]); + + // 3. Increment the popular player count + $stmt = $pdo->prepare( + 'INSERT INTO player_favorites (player_id, favorite_count) VALUES (?, 1) ON DUPLICATE KEY UPDATE favorite_count = favorite_count + 1;' + ); + $stmt->execute([$playerId]); + + $pdo->commit(); + + echo json_encode(['status' => 'success', 'message' => 'Player added to favorites.']); + +} catch (PDOException $e) { + $pdo->rollBack(); + http_response_code(500); + echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]); +} diff --git a/api/check_session.php b/api/check_session.php new file mode 100644 index 0000000..860d2d0 --- /dev/null +++ b/api/check_session.php @@ -0,0 +1,16 @@ + true, + 'user' => [ + 'username' => $_SESSION['username'], + 'role' => $_SESSION['role'] + ] + ]); +} else { + echo json_encode(['loggedIn' => false]); +} diff --git a/api/get_favorites.php b/api/get_favorites.php new file mode 100644 index 0000000..bb37f5e --- /dev/null +++ b/api/get_favorites.php @@ -0,0 +1,30 @@ + 'error', 'message' => 'You must be logged in to view favorites.']); + exit; +} + +$userId = $_SESSION['user_id']; + +try { + $pdo = db(); + + $stmt = $pdo->prepare( + 'SELECT p.* FROM players p JOIN user_favorites uf ON p.id = uf.player_id WHERE uf.user_id = ?' + ); + $stmt->execute([$userId]); + + $favorites = $stmt->fetchAll(); + + echo json_encode(['status' => 'success', 'favorites' => $favorites]); + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]); +} diff --git a/api/get_player_comparison.php b/api/get_player_comparison.php new file mode 100644 index 0000000..281bf63 --- /dev/null +++ b/api/get_player_comparison.php @@ -0,0 +1,60 @@ + 'error', 'message' => 'Two player IDs are required.']); + exit; +} + +$apiKey = '1'; // Free public API key for testing + +// --- URLs for concurrent fetching --- +$p1_url = 'https://www.thesportsdb.com/api/v1/json/' . $apiKey . '/lookupplayer.php?id=' . urlencode($player1Id); +$p2_url = 'https://www.thesportsdb.com/api/v1/json/' . $apiKey . '/lookupplayer.php?id=' . urlencode($player2Id); + +// --- cURL multi-handle for concurrent requests --- +$mh = curl_multi_init(); + +$p1_ch = curl_init($p1_url); +$p2_ch = curl_init($p2_url); + +curl_setopt($p1_ch, CURLOPT_RETURNTRANSFER, 1); +curl_setopt($p2_ch, CURLOPT_RETURNTRANSFER, 1); + +curl_multi_add_handle($mh, $p1_ch); +curl_multi_add_handle($mh, $p2_ch); + +// Execute the handles +$running = null; +do { + curl_multi_exec($mh, $running); +} while ($running); + +curl_multi_remove_handle($mh, $p1_ch); +curl_multi_remove_handle($mh, $p2_ch); +curl_multi_close($mh); + +// --- Process responses --- +$p1_response = curl_multi_getcontent($p1_ch); +$p2_response = curl_multi_getcontent($p2_ch); + +$p1_data = json_decode($p1_response, true); +$p2_data = json_decode($p2_response, true); + +// --- Combine and return data --- +if (isset($p1_data['players'][0]) && isset($p2_data['players'][0])) { + echo json_encode([ + 'status' => 'success', + 'players' => [ + $p1_data['players'][0], + $p2_data['players'][0] + ] + ]); +} else { + http_response_code(404); + echo json_encode(['status' => 'error', 'message' => 'One or both players not found.']); +} diff --git a/api/get_player_details.php b/api/get_player_details.php new file mode 100644 index 0000000..1103e33 --- /dev/null +++ b/api/get_player_details.php @@ -0,0 +1,62 @@ + 'error', 'message' => 'Player ID is required.']); + exit; +} + +$apiKey = '1'; // Free public API key for testing + +// --- URLs for concurrent fetching --- +$detailsUrl = 'https://www.thesportsdb.com/api/v1/json/' . $apiKey . '/lookupplayer.php?id=' . urlencode($playerId); +$eventsUrl = 'https://www.thesportsdb.com/api/v1/json/' . $apiKey . '/lookuppast5events.php?id=' . urlencode($playerId); + +// --- cURL multi-handle for concurrent requests --- +$mh = curl_multi_init(); + +$detailsCh = curl_init($detailsUrl); +$eventsCh = curl_init($eventsUrl); + +curl_setopt($detailsCh, CURLOPT_RETURNTRANSFER, 1); +curl_setopt($eventsCh, CURLOPT_RETURNTRANSFER, 1); + +curl_multi_add_handle($mh, $detailsCh); +curl_multi_add_handle($mh, $eventsCh); + +// Execute the handles +$running = null; +do { + curl_multi_exec($mh, $running); +} while ($running); + +curl_multi_remove_handle($mh, $detailsCh); +curl_multi_remove_handle($mh, $eventsCh); +curl_multi_close($mh); + +// --- Process responses --- +$detailsResponse = curl_multi_getcontent($detailsCh); +$eventsResponse = curl_multi_getcontent($eventsCh); + +$detailsData = json_decode($detailsResponse, true); +$eventsData = json_decode($eventsResponse, true); + +// --- Combine and return data --- +if (isset($detailsData['players']) && count($detailsData['players']) > 0) { + $playerDetails = $detailsData['players'][0]; + $recentGames = $eventsData['results'] ?? []; // 'results' is the key for past events + + echo json_encode([ + 'status' => 'success', + 'player' => [ + 'details' => $playerDetails, + 'recent_games' => $recentGames + ] + ]); +} else { + http_response_code(404); + echo json_encode(['status' => 'error', 'message' => 'Player not found.']); +} \ No newline at end of file diff --git a/api/get_popular_players.php b/api/get_popular_players.php new file mode 100644 index 0000000..7c89140 --- /dev/null +++ b/api/get_popular_players.php @@ -0,0 +1,24 @@ +query( + 'SELECT p.id, p.name, p.team, p.sport, p.image_url, pf.favorite_count + FROM players p + JOIN player_favorites pf ON p.id = pf.player_id + ORDER BY pf.favorite_count DESC + LIMIT 6' + ); + + $popular_players = $stmt->fetchAll(); + + echo json_encode(['players' => $popular_players]); + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]); +} diff --git a/api/get_users.php b/api/get_users.php new file mode 100644 index 0000000..c9249e3 --- /dev/null +++ b/api/get_users.php @@ -0,0 +1,26 @@ + 'error', 'message' => 'Forbidden: Admins only.']); + exit; +} + +try { + $pdo = db(); + + // Select all users but omit the password hash + $stmt = $pdo->query('SELECT id, username, email, role, created_at FROM users ORDER BY created_at DESC'); + $users = $stmt->fetchAll(); + + echo json_encode(['status' => 'success', 'users' => $users]); + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]); +} diff --git a/api/login.php b/api/login.php new file mode 100644 index 0000000..3a60890 --- /dev/null +++ b/api/login.php @@ -0,0 +1,53 @@ + 'error', 'message' => 'Invalid input.']); + exit; +} + +$email = trim($data['email']); +$password = $data['password']; + +if (empty($email) || empty($password)) { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Please fill all fields.']); + exit; +} + +try { + $pdo = db(); + + $stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?'); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password'])) { + // Password is correct, start session + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $user['username']; + $_SESSION['role'] = $user['role']; + + echo json_encode([ + 'status' => 'success', + 'message' => 'Login successful.', + 'user' => [ + 'username' => $user['username'], + 'role' => $user['role'] + ] + ]); + } else { + http_response_code(401); + echo json_encode(['status' => 'error', 'message' => 'Invalid email or password.']); + } + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]); +} diff --git a/api/logout.php b/api/logout.php new file mode 100644 index 0000000..de9e87e --- /dev/null +++ b/api/logout.php @@ -0,0 +1,7 @@ + 'success', 'message' => 'Logged out successfully.']); diff --git a/api/register.php b/api/register.php new file mode 100644 index 0000000..cc59c1b --- /dev/null +++ b/api/register.php @@ -0,0 +1,60 @@ + 'error', 'message' => 'Invalid input.']); + exit; +} + +$username = trim($data['username']); +$email = trim($data['email']); +$password = $data['password']; + +if (empty($username) || empty($email) || empty($password)) { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Please fill all fields.']); + exit; +} + +if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']); + exit; +} + +if (strlen($password) < 6) { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Password must be at least 6 characters long.']); + exit; +} + +try { + $pdo = db(); + + // Check if username or email already exists + $stmt = $pdo->prepare('SELECT id FROM users WHERE username = ? OR email = ?'); + $stmt->execute([$username, $email]); + if ($stmt->fetch()) { + http_response_code(409); + echo json_encode(['status' => 'error', 'message' => 'Username or email already taken.']); + exit; + } + + // Hash the password + $hashedPassword = password_hash($password, PASSWORD_DEFAULT); + + // Insert new user + $stmt = $pdo->prepare('INSERT INTO users (username, email, password) VALUES (?, ?, ?)'); + $stmt->execute([$username, $email, $hashedPassword]); + + echo json_encode(['status' => 'success', 'message' => 'Registration successful. You can now log in.']); + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]); +} diff --git a/api/remove_favorite.php b/api/remove_favorite.php new file mode 100644 index 0000000..afbcf17 --- /dev/null +++ b/api/remove_favorite.php @@ -0,0 +1,39 @@ + 'error', 'message' => 'You must be logged in to remove favorites.']); + exit; +} + +$userId = $_SESSION['user_id']; +$data = json_decode(file_get_contents('php://input'), true); + +if (!$data || !isset($data['player_id'])) { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Invalid player data.']); + exit; +} + +$playerId = $data['player_id']; + +try { + $pdo = db(); + + $stmt = $pdo->prepare('DELETE FROM user_favorites WHERE user_id = ? AND player_id = ?'); + $stmt->execute([$userId, $playerId]); + + if ($stmt->rowCount() > 0) { + echo json_encode(['status' => 'success', 'message' => 'Player removed from favorites.']); + } else { + echo json_encode(['status' => 'error', 'message' => 'Player not found in your favorites.']); + } + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]); +} diff --git a/api/search.php b/api/search.php new file mode 100644 index 0000000..61ddf32 --- /dev/null +++ b/api/search.php @@ -0,0 +1,20 @@ + []]); + exit; +} + +$apiKey = '1'; // Free public API key for testing +$url = 'https://www.thesportsdb.com/api/v1/json/' . $apiKey . '/searchplayers.php?p=' . urlencode($playerName); + +$ch = curl_init(); +curl_setopt($ch, CURLOPT_URL, $url); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); +$response = curl_exec($ch); +curl_close($ch); + +echo $response; diff --git a/api/update_user_role.php b/api/update_user_role.php new file mode 100644 index 0000000..3d256de --- /dev/null +++ b/api/update_user_role.php @@ -0,0 +1,54 @@ + 'error', 'message' => 'Forbidden: Admins only.']); + exit; +} + +$data = json_decode(file_get_contents('php://input'), true); + +if (!$data || !isset($data['user_id']) || !isset($data['role'])) { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Invalid input. User ID and role are required.']); + exit; +} + +$userIdToUpdate = $data['user_id']; +$newRole = $data['role']; +$allowedRoles = ['regular', 'sports_analyst', 'sports_bettor', 'admin']; + +if (!in_array($newRole, $allowedRoles)) { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Invalid role specified.']); + exit; +} + +// Prevent admin from accidentally changing their own role and getting locked out +if ($userIdToUpdate == $_SESSION['user_id'] && $newRole !== 'admin') { + http_response_code(400); + echo json_encode(['status' => 'error', 'message' => 'Admins cannot remove their own admin status.']); + exit; +} + +try { + $pdo = db(); + + $stmt = $pdo->prepare('UPDATE users SET role = ? WHERE id = ?'); + $stmt->execute([$newRole, $userIdToUpdate]); + + if ($stmt->rowCount() > 0) { + echo json_encode(['status' => 'success', 'message' => 'User role updated successfully.']); + } else { + echo json_encode(['status' => 'error', 'message' => 'User not found or role is already set.']); + } + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]); +} diff --git a/assets/css/custom.css b/assets/css/custom.css index ed8f16b..db5f1dd 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,3 +1,4 @@ + body { background: linear-gradient(to bottom, #000000, #004B4B); color: #FFFFFF; @@ -5,7 +6,7 @@ body { } h1, h2, h3, h4, h5, h6 { - font-family: Georgia, 'Times New Roman', Times, serif; + font-family: 'Poppins', sans-serif; color: #00FFFF; } @@ -74,3 +75,31 @@ h1, h2, h3, h4, h5, h6 { color: #AAAAAA; font-size: 0.9rem; } + +.player-link { + text-decoration: none; + color: inherit; +} + +.player-link:hover { + color: inherit; +} + +.search-results-popover { + position: absolute; + background: #0D1A1A; + border: 1px solid rgba(0, 255, 255, 0.2); + border-radius: 8px; + z-index: 100; + max-height: 300px; + overflow-y: auto; +} + +.search-results-popover .result-item { + padding: 8px 12px; + cursor: pointer; +} + +.search-results-popover .result-item:hover { + background: rgba(0, 255, 255, 0.1); +} diff --git a/assets/js/admin.js b/assets/js/admin.js new file mode 100644 index 0000000..0b66224 --- /dev/null +++ b/assets/js/admin.js @@ -0,0 +1,127 @@ +document.addEventListener('DOMContentLoaded', function() { + + let usersData = []; // To store user data for CSV export + + const userTableContainer = document.getElementById('user-table-container'); + const authLinks = document.getElementById('auth-links'); + const downloadBtn = document.getElementById('download-users-csv'); + + // --- Auth Logic (for header consistency) --- + const updateAuthUI = (user) => { + if (user) { + let adminLink = user.role === 'admin' ? 'Admin' : ''; + authLinks.innerHTML = ` + ${adminLink} + Welcome, ${user.username} + + `; + document.getElementById('logout-btn').addEventListener('click', logout); + } else { + authLinks.innerHTML = ` + Login + Register + `; + } + }; + + const logout = () => { + fetch('/api/logout.php').then(() => window.location.href = '/'); + }; + + const checkSession = () => { + fetch('/api/check_session.php') + .then(response => response.json()) + .then(data => { + updateAuthUI(data.loggedIn ? data.user : null); + }); + }; + + // --- User Management Logic --- + + const renderUserTable = (users) => { + const roles = ['regular', 'sports_analyst', 'sports_bettor', 'admin']; + let tableHtml = ` + + + + + + + + + + + + + `; + + users.forEach(user => { + tableHtml += ` + + + + + + + + + `; + }); + + tableHtml += '
IDUsernameEmailRoleJoinedAction
${user.id}${user.username}${user.email} + + ${new Date(user.created_at).toLocaleDateString()}
'; + userTableContainer.innerHTML = tableHtml; + + document.querySelectorAll('.save-role-btn').forEach(button => { + button.addEventListener('click', function() { + const userId = this.dataset.userId; + const roleSelector = document.querySelector(`select[data-user-id="${userId}"]`); + const newRole = roleSelector.value; + updateUserRole(userId, newRole); + }); + }); + }; + + const updateUserRole = (userId, role) => { + fetch('/api/update_user_role.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ user_id: userId, role: role }) + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + alert('User role updated!'); + } else { + alert(`Error: ${data.message}`); + } + }); + }; + + const loadUsers = () => { + fetch('/api/get_users.php') + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + usersData = data.users; // Store for CSV export + renderUserTable(usersData); + } else { + userTableContainer.innerHTML = `
${data.message}
`; + } + }); + }; + + // --- Event Listeners --- + if (downloadBtn) { + downloadBtn.addEventListener('click', () => { + exportToCsv('stattracker_users.csv', usersData); + }); + } + + // --- Initial Load --- + checkSession(); + loadUsers(); +}); \ No newline at end of file diff --git a/assets/js/auth.js b/assets/js/auth.js new file mode 100644 index 0000000..ecf4a76 --- /dev/null +++ b/assets/js/auth.js @@ -0,0 +1,66 @@ + +document.addEventListener('DOMContentLoaded', function() { + const registerForm = document.getElementById('register-form'); + const loginForm = document.getElementById('login-form'); + const messageDiv = document.getElementById('message'); + + const showMessage = (message, isError = false) => { + messageDiv.textContent = message; + messageDiv.className = isError ? 'alert alert-danger' : 'alert alert-success'; + }; + + if (registerForm) { + registerForm.addEventListener('submit', function(e) { + e.preventDefault(); + const username = document.getElementById('username').value; + const email = document.getElementById('email').value; + const password = document.getElementById('password').value; + + fetch('/api/register.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, email, password }) + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + showMessage(data.message); + setTimeout(() => window.location.href = 'login.php', 2000); + } else { + showMessage(data.message, true); + } + }) + .catch(error => { + console.error('Registration error:', error); + showMessage('An unexpected error occurred.', true); + }); + }); + } + + if (loginForm) { + loginForm.addEventListener('submit', function(e) { + e.preventDefault(); + const email = document.getElementById('email').value; + const password = document.getElementById('password').value; + + fetch('/api/login.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }) + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + showMessage(data.message); + setTimeout(() => window.location.href = 'index.php', 1500); + } else { + showMessage(data.message, true); + } + }) + .catch(error => { + console.error('Login error:', error); + showMessage('An unexpected error occurred.', true); + }); + }); + } +}); diff --git a/assets/js/compare.js b/assets/js/compare.js new file mode 100644 index 0000000..2373339 --- /dev/null +++ b/assets/js/compare.js @@ -0,0 +1,111 @@ + +document.addEventListener('DOMContentLoaded', function() { + + const comparisonContainer = document.getElementById('comparison-container'); + const authLinks = document.getElementById('auth-links'); + + // --- Auth Logic --- + const updateAuthUI = (user) => { + if (user) { + let adminLink = user.role === 'admin' ? 'Admin' : ''; + authLinks.innerHTML = ` + ${adminLink} + Welcome, ${user.username} + + `; + document.getElementById('logout-btn').addEventListener('click', logout); + } else { + authLinks.innerHTML = ` + Login + Register + `; + } + }; + + const logout = () => { + fetch('/api/logout.php').then(() => window.location.href = '/'); + }; + + const checkSession = () => { + fetch('/api/check_session.php') + .then(response => response.json()) + .then(data => { + updateAuthUI(data.loggedIn ? data.user : null); + }); + }; + + // --- Comparison Logic --- + const renderComparison = (players) => { + const [p1, p2] = players; + + comparisonContainer.innerHTML = ` +
+
+ ${p1.strPlayer} +

${p1.strPlayer}

+

${p1.strTeam}

+
+
+
+
+ ${p2.strPlayer} +

${p2.strPlayer}

+

${p2.strTeam}

+
+
+
+
+

Stat Comparison

+ + + ${createComparisonRow('Position', p1.strPosition, p2.strPosition)} + ${createComparisonRow('Height', p1.strHeight, p2.strHeight)} + ${createComparisonRow('Weight', p1.strWeight, p2.strWeight)} + ${createComparisonRow('Nationality', p1.strNationality, p2.strNationality)} + ${createComparisonRow('Date of Birth', p1.dateBorn, p2.dateBorn)} + +
+
+
+ `; + }; + + const createComparisonRow = (label, val1, val2) => { + return ` + + ${val1 || 'N/A'} + ${label} + ${val2 || 'N/A'} + + `; + }; + + const loadComparisonData = () => { + const urlParams = new URLSearchParams(window.location.search); + const p1Id = urlParams.get('p1'); + const p2Id = urlParams.get('p2'); + + if (!p1Id || !p2Id) { + comparisonContainer.innerHTML = '
Two player IDs are required for comparison.
'; + return; + } + + fetch(`/api/get_player_comparison.php?p1=${p1Id}&p2=${p2Id}`) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + renderComparison(data.players); + } else { + comparisonContainer.innerHTML = `
${data.message}
`; + } + }) + .catch(error => { + console.error('Error loading comparison data:', error); + comparisonContainer.innerHTML = '
An error occurred.
'; + }); + }; + + // --- Initial Load --- + checkSession(); + loadComparisonData(); +}); diff --git a/assets/js/csv-export.js b/assets/js/csv-export.js new file mode 100644 index 0000000..5ba56f2 --- /dev/null +++ b/assets/js/csv-export.js @@ -0,0 +1,30 @@ +function exportToCsv(filename, data) { + if (!data || data.length === 0) { + alert('No data to export.'); + return; + } + + const headers = Object.keys(data[0]); + const csvRows = []; + csvRows.push(headers.join(',')); + + for (const row of data) { + const values = headers.map(header => { + const escaped = ('' + row[header]).replace(/"/g, '"'); + return `"${escaped}"`; + }); + csvRows.push(values.join(',')); + } + + const csvString = csvRows.join('\n'); + const blob = new Blob([csvString], { type: 'text/csv' }); + const url = window.URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.setAttribute('hidden', ''); + a.setAttribute('href', url); + a.setAttribute('download', filename); + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index 724909c..d411637 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1 +1,279 @@ -// Future JavaScript for interactivity +document.addEventListener('DOMContentLoaded', function() { + + // --- Global State --- + let myFavorites = []; // Holds the user's favorite players state + let comparisonPlayer1 = null; + let comparisonPlayer2 = null; + + // --- DOM Elements --- + const authLinks = document.getElementById('auth-links'); + const searchInput = document.getElementById('playerSearch'); + const searchResultsSection = document.getElementById('search-results'); + const searchResultsContainer = document.getElementById('search-results-container'); + const favoritePlayersSection = document.getElementById('favorite-players'); + const favoritePlayersContainer = favoritePlayersSection.querySelector('.row'); + const popularPlayersSection = document.getElementById('popular-players'); + const popularPlayersContainer = popularPlayersSection.querySelector('.row'); + // Comparison elements + const player1SearchInput = document.getElementById('player1-search'); + const player2SearchInput = document.getElementById('player2-search'); + const player1Results = document.getElementById('player1-results'); + const player2Results = document.getElementById('player2-results'); + const compareBtn = document.getElementById('compare-btn'); + + // --- Auth Logic --- + const updateAuthUI = (user) => { + if (user) { + let adminLink = user.role === 'admin' ? 'Admin' : ''; + authLinks.innerHTML = ` + ${adminLink} + Welcome, ${user.username} + + `; + document.getElementById('logout-btn').addEventListener('click', logout); + favoritePlayersSection.style.display = 'block'; + loadFavorites(); + } else { + authLinks.innerHTML = ` + Login + Register + `; + favoritePlayersSection.style.display = 'none'; + } + }; + + const logout = () => { + fetch('/api/logout.php').then(() => { + myFavorites = []; + window.location.reload(); + }); + }; + + const checkSession = () => { + fetch('/api/check_session.php') + .then(response => response.json()) + .then(data => { + updateAuthUI(data.loggedIn ? data.user : null); + }); + }; + + // --- Comparison Logic --- + const setupComparisonSearch = (input, resultsContainer, playerNumber) => { + input.addEventListener('keyup', function() { + const searchTerm = input.value.trim(); + if (searchTerm.length < 3) { + resultsContainer.innerHTML = ''; + resultsContainer.style.display = 'none'; + return; + } + + fetch(`/api/search.php?name=${encodeURIComponent(searchTerm)}`) + .then(response => response.json()) + .then(data => { + resultsContainer.innerHTML = ''; + if (data.player) { + resultsContainer.style.display = 'block'; + data.player.slice(0, 5).forEach(player => { + const item = document.createElement('div'); + item.classList.add('result-item'); + item.textContent = `${player.strPlayer} (${player.strTeam})`; + item.addEventListener('click', () => { + if (playerNumber === 1) { + comparisonPlayer1 = player; + input.value = player.strPlayer; + } else { + comparisonPlayer2 = player; + input.value = player.strPlayer; + } + resultsContainer.innerHTML = ''; + resultsContainer.style.display = 'none'; + checkCompareReady(); + }); + resultsContainer.appendChild(item); + }); + } else { + resultsContainer.style.display = 'none'; + } + }); + }); + }; + + const checkCompareReady = () => { + if (comparisonPlayer1 && comparisonPlayer2) { + compareBtn.disabled = false; + } + }; + + if (compareBtn) { + compareBtn.addEventListener('click', () => { + if (comparisonPlayer1 && comparisonPlayer2) { + window.location.href = `/compare.php?p1=${comparisonPlayer1.idPlayer}&p2=${comparisonPlayer2.idPlayer}`; + } + }); + } + + // --- Database-backed Favorites Logic (My Favorites) --- + const loadFavorites = () => { + fetch('/api/get_favorites.php') + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + myFavorites = data.favorites; + renderFavorites(); + } + }); + }; + + const renderFavorites = () => { + favoritePlayersContainer.innerHTML = ''; + if (myFavorites.length === 0) { + favoritePlayersContainer.innerHTML = '

No favorite players added yet.

'; + return; + } + myFavorites.forEach(player => { + const playerCard = ` +
+ +
+ Headshot of ${player.name} +
${player.name}
+
${player.team}
+
+
+ +
+ `; + favoritePlayersContainer.innerHTML += playerCard; + }); + document.querySelectorAll('.remove-favorite-btn').forEach(button => { + button.addEventListener('click', function() { + removeFromFavorites(this.dataset.playerId); + }); + }); + }; + + const isFavorite = (playerId) => { + return myFavorites.some(p => p.id == playerId); + }; + + const addToFavorites = (player) => { + fetch('/api/add_favorite.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(player) }) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + myFavorites.push({ id: player.idPlayer, name: player.strPlayer, team: player.strTeam, image_url: player.strCutout }); + renderFavorites(); + loadPopularPlayers(); + } else { + alert(data.message); + } + }); + }; + + const removeFromFavorites = (playerId) => { + fetch('/api/remove_favorite.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ player_id: playerId }) }) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + myFavorites = myFavorites.filter(p => p.id != playerId); + renderFavorites(); + } + }); + }; + + // --- Database Logic (Popular Players) --- + const renderPopularPlayers = (players) => { + popularPlayersContainer.innerHTML = ''; + if (!players || players.length === 0) { + popularPlayersContainer.innerHTML = '

No popular players yet.

'; + return; + } + players.forEach(player => { + const playerCard = ` +
+ +
+ Headshot of ${player.name} +
${player.name}
+
${player.team}
+
Favorited: ${player.favorite_count} times
+
+
+
+ `; + popularPlayersContainer.innerHTML += playerCard; + }); + }; + + const loadPopularPlayers = () => { + fetch('/api/get_popular_players.php') + .then(response => response.json()) + .then(data => { + if (data.players) renderPopularPlayers(data.players); + }) + .catch(error => console.error('Error loading popular players:', error)); + }; + + // --- Main Search Logic --- + if (searchInput) { + searchInput.addEventListener('keyup', function() { + const searchTerm = searchInput.value.trim(); + if (searchTerm.length > 2) { + document.getElementById('favorite-players').style.display = 'none'; + document.getElementById('popular-players').style.display = 'none'; + searchResultsSection.style.display = 'block'; + fetch(`/api/search.php?name=${encodeURIComponent(searchTerm)}`) + .then(response => response.json()) + .then(data => { + searchResultsContainer.innerHTML = ''; + if (data.player) { + data.player.forEach(player => { + const disabled = isFavorite(player.idPlayer) ? 'disabled' : ''; + const playerCard = ` +
+ +
+ Headshot of ${player.strPlayer} +
${player.strPlayer}
+
${player.strTeam}
+
+
+
+ +
+
+ `; + searchResultsContainer.innerHTML += playerCard; + }); + document.querySelectorAll('.add-favorite-btn').forEach(button => { + button.addEventListener('click', function() { + const playerData = JSON.parse(this.dataset.player); + addToFavorites(playerData); + this.textContent = 'Favorited'; + this.disabled = true; + }); + }); + } else { + searchResultsContainer.innerHTML = '

No players found.

'; + } + }); + } else { + document.getElementById('favorite-players').style.display = 'block'; + document.getElementById('popular-players').style.display = 'block'; + searchResultsSection.style.display = 'none'; + searchResultsContainer.innerHTML = ''; + } + }); + } + + // --- Initial Render --- + checkSession(); + loadPopularPlayers(); + if (player1SearchInput && player2SearchInput) { + setupComparisonSearch(player1SearchInput, player1Results, 1); + setupComparisonSearch(player2SearchInput, player2Results, 2); + } +}); \ No newline at end of file diff --git a/assets/js/player-detail.js b/assets/js/player-detail.js new file mode 100644 index 0000000..5990325 --- /dev/null +++ b/assets/js/player-detail.js @@ -0,0 +1,147 @@ +document.addEventListener('DOMContentLoaded', function() { + + let gamesData = []; // To store games data for CSV export + + const playerDetailContainer = document.getElementById('player-detail-container'); + const authLinks = document.getElementById('auth-links'); + + // --- Auth Logic --- + const updateAuthUI = (user) => { + if (user) { + authLinks.innerHTML = ` + Welcome, ${user.username} + + `; + document.getElementById('logout-btn').addEventListener('click', logout); + } else { + authLinks.innerHTML = ` + Login + Register + `; + } + }; + + const logout = () => { + fetch('/api/logout.php').then(() => window.location.href = '/'); + }; + + const checkSession = () => { + fetch('/api/check_session.php') + .then(response => response.json()) + .then(data => { + updateAuthUI(data.loggedIn ? data.user : null); + }); + }; + + // --- Player Detail Logic --- + const renderPlayerDetails = (playerData) => { + const details = playerData.details; + gamesData = playerData.recent_games; // Store for export + + let biographyHtml = ` +
+
+
+ ${details.strPlayer} +

${details.strPlayer}

+

${details.strTeam}

+
+
+
+
+

Biography

+

${details.strDescriptionEN || 'No biography available.'}

+
+
+
+

Position: ${details.strPosition}

+

Height: ${details.strHeight}

+

Weight: ${details.strWeight}

+
+
+

Nationality: ${details.strNationality}

+

Date of Birth: ${details.dateBorn}

+

Sport: ${details.strSport}

+
+
+
+
+
+ `; + + let gamesHtml = ''; + if (gamesData && gamesData.length > 0) { + gamesHtml = ` +
+
+
+
+

Recent Games

+ +
+ + + + + + + + + + + + ${gamesData.map(game => ` + + + + + + + + `).join('')} + +
DateEventHome TeamAway TeamScore
${game.dateEvent}${game.strEvent}${game.strHomeTeam}${game.strAwayTeam}${game.intHomeScore} - ${game.intAwayScore}
+
+
+
+ `; + } + + playerDetailContainer.innerHTML = biographyHtml + gamesHtml; + + const downloadBtn = document.getElementById('download-games-csv'); + if (downloadBtn) { + downloadBtn.addEventListener('click', () => { + exportToCsv(`${details.strPlayer}_recent_games.csv', gamesData); + }); + } + }; + + const loadPlayerDetails = () => { + const urlParams = new URLSearchParams(window.location.search); + const playerId = urlParams.get('id'); + + if (!playerId) { + playerDetailContainer.innerHTML = '
No player ID provided.
'; + return; + } + + fetch(`/api/get_player_details.php?id=${playerId}`) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + renderPlayerDetails(data.player); + } else { + playerDetailContainer.innerHTML = `
${data.message}
`; + } + }) + .catch(error => { + console.error('Error loading player details:', error); + playerDetailContainer.innerHTML = '
An error occurred while fetching player data.
'; + }); + }; + + // --- Initial Load --- + checkSession(); + loadPlayerDetails(); +}); \ No newline at end of file diff --git a/compare.php b/compare.php new file mode 100644 index 0000000..c618b3a --- /dev/null +++ b/compare.php @@ -0,0 +1,47 @@ + + + + + + + Compare Players - StatTracker + + + + + + + + +
+
+ +
+ +
+

Player Comparison

+
+ +
+
+ Loading... +
+
+
+
+ + +
+ + + + diff --git a/db/setup.php b/db/setup.php new file mode 100644 index 0000000..04c3ef8 --- /dev/null +++ b/db/setup.php @@ -0,0 +1,49 @@ +exec($command); + } + + echo "Tables created successfully.\n"; + +} catch (PDOException $e) { + die("Database setup failed: " . $e->getMessage()); +} + diff --git a/index.php b/index.php index 1a9cfe1..069337f 100644 --- a/index.php +++ b/index.php @@ -13,65 +13,73 @@ + + +
-
-

StatTracker

-

Your personal sports stats dashboard.

-