basic2
This commit is contained in:
parent
108457b846
commit
2587a17d37
59
admin.php
Normal file
59
admin.php
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// Role-based access control
|
||||
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
|
||||
// If user is not logged in or is not an admin, redirect to homepage
|
||||
header('Location: /');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Dashboard - StatTracker</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container my-5">
|
||||
<header class="mb-5">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">StatTracker</a>
|
||||
<div id="auth-links" class="d-flex">
|
||||
<!-- Auth links are injected by JS -->
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Admin Dashboard</h1>
|
||||
<button id="download-users-csv" class="btn btn-success">Download Users as CSV</button>
|
||||
</div>
|
||||
<section id="user-management">
|
||||
<h2>User Management</h2>
|
||||
<div id="user-table-container" class="player-card p-4">
|
||||
<!-- User table will be loaded here by admin.js -->
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer text-center">
|
||||
<p>© <?php echo date("Y"); ?> StatTracker. All Rights Reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/csv-export.js?v=<?php echo time(); ?>"></script>
|
||||
<script src="assets/js/admin.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
58
api/add_favorite.php
Normal file
58
api/add_favorite.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['status' => '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()]);
|
||||
}
|
||||
16
api/check_session.php
Normal file
16
api/check_session.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
echo json_encode([
|
||||
'loggedIn' => true,
|
||||
'user' => [
|
||||
'username' => $_SESSION['username'],
|
||||
'role' => $_SESSION['role']
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['loggedIn' => false]);
|
||||
}
|
||||
30
api/get_favorites.php
Normal file
30
api/get_favorites.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['status' => '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()]);
|
||||
}
|
||||
60
api/get_player_comparison.php
Normal file
60
api/get_player_comparison.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$player1Id = $_GET['p1'] ?? '';
|
||||
$player2Id = $_GET['p2'] ?? '';
|
||||
|
||||
if (empty($player1Id) || empty($player2Id)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => '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.']);
|
||||
}
|
||||
62
api/get_player_details.php
Normal file
62
api/get_player_details.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$playerId = $_GET['id'] ?? '';
|
||||
|
||||
if (empty($playerId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => '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.']);
|
||||
}
|
||||
24
api/get_popular_players.php
Normal file
24
api/get_popular_players.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
$stmt = $pdo->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()]);
|
||||
}
|
||||
26
api/get_users.php
Normal file
26
api/get_users.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Admin-only endpoint
|
||||
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => '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()]);
|
||||
}
|
||||
53
api/login.php
Normal file
53
api/login.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$data || !isset($data['email']) || !isset($data['password'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => '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()]);
|
||||
}
|
||||
7
api/logout.php
Normal file
7
api/logout.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_unset();
|
||||
session_destroy();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['status' => 'success', 'message' => 'Logged out successfully.']);
|
||||
60
api/register.php
Normal file
60
api/register.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$data || !isset($data['email']) || !isset($data['password']) || !isset($data['username'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => '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()]);
|
||||
}
|
||||
39
api/remove_favorite.php
Normal file
39
api/remove_favorite.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['status' => '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()]);
|
||||
}
|
||||
20
api/search.php
Normal file
20
api/search.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$playerName = $_GET['name'] ?? '';
|
||||
|
||||
if (empty($playerName)) {
|
||||
echo json_encode(['player' => []]);
|
||||
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;
|
||||
54
api/update_user_role.php
Normal file
54
api/update_user_role.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Admin-only endpoint
|
||||
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => '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()]);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
127
assets/js/admin.js
Normal file
127
assets/js/admin.js
Normal file
@ -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' ? '<a href="/admin.php" class="btn btn-outline-warning me-3">Admin</a>' : '';
|
||||
authLinks.innerHTML = `
|
||||
${adminLink}
|
||||
<span class="navbar-text me-3">Welcome, ${user.username}</span>
|
||||
<button id="logout-btn" class="btn btn-outline-light">Logout</button>
|
||||
`;
|
||||
document.getElementById('logout-btn').addEventListener('click', logout);
|
||||
} else {
|
||||
authLinks.innerHTML = `
|
||||
<a href="/login.php" class="btn btn-outline-light me-2">Login</a>
|
||||
<a href="/register.php" class="btn btn-light">Register</a>
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
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 = `
|
||||
<table class="table table-dark table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Joined</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
users.forEach(user => {
|
||||
tableHtml += `
|
||||
<tr>
|
||||
<td>${user.id}</td>
|
||||
<td>${user.username}</td>
|
||||
<td>${user.email}</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm" data-user-id="${user.id}">
|
||||
${roles.map(role => `<option value="${role}" ${user.role === role ? 'selected' : ''}>${role}</option>`).join('')}
|
||||
</select>
|
||||
</td>
|
||||
<td>${new Date(user.created_at).toLocaleDateString()}</td>
|
||||
<td><button class="btn btn-sm btn-primary save-role-btn" data-user-id="${user.id}">Save</button></td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
tableHtml += '</tbody></table>';
|
||||
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 = `<div class="alert alert-danger">${data.message}</div>`;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// --- Event Listeners ---
|
||||
if (downloadBtn) {
|
||||
downloadBtn.addEventListener('click', () => {
|
||||
exportToCsv('stattracker_users.csv', usersData);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Initial Load ---
|
||||
checkSession();
|
||||
loadUsers();
|
||||
});
|
||||
66
assets/js/auth.js
Normal file
66
assets/js/auth.js
Normal file
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
111
assets/js/compare.js
Normal file
111
assets/js/compare.js
Normal file
@ -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' ? '<a href="/admin.php" class="btn btn-outline-warning me-3">Admin</a>' : '';
|
||||
authLinks.innerHTML = `
|
||||
${adminLink}
|
||||
<span class="navbar-text me-3">Welcome, ${user.username}</span>
|
||||
<button id="logout-btn" class="btn btn-outline-light">Logout</button>
|
||||
`;
|
||||
document.getElementById('logout-btn').addEventListener('click', logout);
|
||||
} else {
|
||||
authLinks.innerHTML = `
|
||||
<a href="/login.php" class="btn btn-outline-light me-2">Login</a>
|
||||
<a href="/register.php" class="btn btn-light">Register</a>
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
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 = `
|
||||
<div class="col-md-6">
|
||||
<div class="player-card text-center">
|
||||
<img src="${p1.strCutout || p1.strThumb}" class="img-fluid rounded-circle" alt="${p1.strPlayer}">
|
||||
<h2 class="mt-3">${p1.strPlayer}</h2>
|
||||
<p class="text-secondary">${p1.strTeam}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="player-card text-center">
|
||||
<img src="${p2.strCutout || p2.strThumb}" class="img-fluid rounded-circle" alt="${p2.strPlayer}">
|
||||
<h2 class="mt-3">${p2.strPlayer}</h2>
|
||||
<p class="text-secondary">${p2.strTeam}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mt-4">
|
||||
<div class="player-card">
|
||||
<h3 class="text-center">Stat Comparison</h3>
|
||||
<table class="table table-dark table-striped">
|
||||
<tbody>
|
||||
${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)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
const createComparisonRow = (label, val1, val2) => {
|
||||
return `
|
||||
<tr>
|
||||
<td class="text-end">${val1 || 'N/A'}</td>
|
||||
<th class="text-center">${label}</th>
|
||||
<td>${val2 || 'N/A'}</td>
|
||||
</tr>
|
||||
`;
|
||||
};
|
||||
|
||||
const loadComparisonData = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const p1Id = urlParams.get('p1');
|
||||
const p2Id = urlParams.get('p2');
|
||||
|
||||
if (!p1Id || !p2Id) {
|
||||
comparisonContainer.innerHTML = '<div class="alert alert-danger">Two player IDs are required for comparison.</div>';
|
||||
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 = `<div class="alert alert-danger">${data.message}</div>`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading comparison data:', error);
|
||||
comparisonContainer.innerHTML = '<div class="alert alert-danger">An error occurred.</div>';
|
||||
});
|
||||
};
|
||||
|
||||
// --- Initial Load ---
|
||||
checkSession();
|
||||
loadComparisonData();
|
||||
});
|
||||
30
assets/js/csv-export.js
Normal file
30
assets/js/csv-export.js
Normal file
@ -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);
|
||||
}
|
||||
@ -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' ? '<a href="/admin.php" class="btn btn-outline-warning me-3">Admin</a>' : '';
|
||||
authLinks.innerHTML = `
|
||||
${adminLink}
|
||||
<span class="navbar-text me-3">Welcome, ${user.username}</span>
|
||||
<button id="logout-btn" class="btn btn-outline-light">Logout</button>
|
||||
`;
|
||||
document.getElementById('logout-btn').addEventListener('click', logout);
|
||||
favoritePlayersSection.style.display = 'block';
|
||||
loadFavorites();
|
||||
} else {
|
||||
authLinks.innerHTML = `
|
||||
<a href="/login.php" class="btn btn-outline-light me-2">Login</a>
|
||||
<a href="/register.php" class="btn btn-light">Register</a>
|
||||
`;
|
||||
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 = '<div class="col"><p class="text-center text-secondary">No favorite players added yet.</p></div>';
|
||||
return;
|
||||
}
|
||||
myFavorites.forEach(player => {
|
||||
const playerCard = `
|
||||
<div class="col-md-4 player-column">
|
||||
<a href="/player.php?id=${player.id}" class="player-link">
|
||||
<div class="player-card text-center">
|
||||
<img src="${player.image_url || 'https://picsum.photos/seed/placeholder/150/150'}" alt="Headshot of ${player.name}" class="img-fluid">
|
||||
<div class="player-name">${player.name}</div>
|
||||
<div class="player-team">${player.team}</div>
|
||||
</div>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-danger mt-3 remove-favorite-btn" data-player-id="${player.id}">Remove</button>
|
||||
</div>
|
||||
`;
|
||||
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 = '<div class="col"><p class="text-center text-secondary">No popular players yet.</p></div>';
|
||||
return;
|
||||
}
|
||||
players.forEach(player => {
|
||||
const playerCard = `
|
||||
<div class="col-md-4 player-column">
|
||||
<a href="/player.php?id=${player.id}" class="player-link">
|
||||
<div class="player-card text-center">
|
||||
<img src="${player.image_url || 'https://picsum.photos/seed/placeholder/150/150'}" alt="Headshot of ${player.name}" class="img-fluid">
|
||||
<div class="player-name">${player.name}</div>
|
||||
<div class="player-team">${player.team}</div>
|
||||
<div class="player-stats">Favorited: ${player.favorite_count} times</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
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 = `
|
||||
<div class="col-md-4 player-column">
|
||||
<a href="/player.php?id=${player.idPlayer}" class="player-link">
|
||||
<div class="player-card text-center">
|
||||
<img src="${player.strCutout || 'https://picsum.photos/seed/placeholder/150/150'}" alt="Headshot of ${player.strPlayer}" class="img-fluid">
|
||||
<div class="player-name">${player.strPlayer}</div>
|
||||
<div class="player-team">${player.strTeam}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-sm btn-outline-info mt-2 add-favorite-btn"
|
||||
data-player='${JSON.stringify(player)}' ${disabled}>
|
||||
${disabled ? 'Favorited' : 'Add to Favorites'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
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 = '<p class="text-center text-light">No players found.</p>';
|
||||
}
|
||||
});
|
||||
} 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);
|
||||
}
|
||||
});
|
||||
147
assets/js/player-detail.js
Normal file
147
assets/js/player-detail.js
Normal file
@ -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 = `
|
||||
<span class="navbar-text me-3">Welcome, ${user.username}</span>
|
||||
<button id="logout-btn" class="btn btn-outline-light">Logout</button>
|
||||
`;
|
||||
document.getElementById('logout-btn').addEventListener('click', logout);
|
||||
} else {
|
||||
authLinks.innerHTML = `
|
||||
<a href="/login.php" class="btn btn-outline-light me-2">Login</a>
|
||||
<a href="/register.php" class="btn btn-light">Register</a>
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
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 = `
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="player-card text-center">
|
||||
<img src="${details.strCutout || details.strThumb || 'https://picsum.photos/seed/placeholder/400/400'}" class="img-fluid rounded-circle" alt="${details.strPlayer}">
|
||||
<h2 class="mt-3">${details.strPlayer}</h2>
|
||||
<p class="text-secondary">${details.strTeam}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="player-card">
|
||||
<h3>Biography</h3>
|
||||
<p>${details.strDescriptionEN || 'No biography available.'}</p>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p><strong>Position:</strong> ${details.strPosition}</p>
|
||||
<p><strong>Height:</strong> ${details.strHeight}</p>
|
||||
<p><strong>Weight:</strong> ${details.strWeight}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p><strong>Nationality:</strong> ${details.strNationality}</p>
|
||||
<p><strong>Date of Birth:</strong> ${details.dateBorn}</p>
|
||||
<p><strong>Sport:</strong> ${details.strSport}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let gamesHtml = '';
|
||||
if (gamesData && gamesData.length > 0) {
|
||||
gamesHtml = `
|
||||
<div class="row mt-5">
|
||||
<div class="col-12">
|
||||
<div class="player-card">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>Recent Games</h3>
|
||||
<button id="download-games-csv" class="btn btn-sm btn-success">Download as CSV</button>
|
||||
</div>
|
||||
<table class="table table-dark table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Event</th>
|
||||
<th>Home Team</th>
|
||||
<th>Away Team</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${gamesData.map(game => `
|
||||
<tr>
|
||||
<td>${game.dateEvent}</td>
|
||||
<td>${game.strEvent}</td>
|
||||
<td>${game.strHomeTeam}</td>
|
||||
<td>${game.strAwayTeam}</td>
|
||||
<td>${game.intHomeScore} - ${game.intAwayScore}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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 = '<div class="alert alert-danger">No player ID provided.</div>';
|
||||
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 = `<div class="alert alert-danger">${data.message}</div>`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading player details:', error);
|
||||
playerDetailContainer.innerHTML = '<div class="alert alert-danger">An error occurred while fetching player data.</div>';
|
||||
});
|
||||
};
|
||||
|
||||
// --- Initial Load ---
|
||||
checkSession();
|
||||
loadPlayerDetails();
|
||||
});
|
||||
47
compare.php
Normal file
47
compare.php
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Compare Players - StatTracker</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container my-5">
|
||||
<header class="mb-5">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">StatTracker</a>
|
||||
<div id="auth-links" class="d-flex">
|
||||
<!-- Auth links are injected by JS -->
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<h1 class="text-center mb-4">Player Comparison</h1>
|
||||
<div id="comparison-container" class="row">
|
||||
<!-- Comparison details will be loaded here by compare.js -->
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-light" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer text-center">
|
||||
<p>© <?php echo date("Y"); ?> StatTracker. All Rights Reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/compare.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
49
db/setup.php
Normal file
49
db/setup.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
echo "Connected to database successfully.\n";
|
||||
|
||||
$commands = [
|
||||
'CREATE TABLE IF NOT EXISTS players (
|
||||
id INT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
team VARCHAR(255),
|
||||
sport VARCHAR(255),
|
||||
image_url VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;',
|
||||
'CREATE TABLE IF NOT EXISTS player_favorites (
|
||||
player_id INT PRIMARY KEY,
|
||||
favorite_count INT DEFAULT 1 NOT NULL,
|
||||
FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;',
|
||||
'CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role ENUM("regular", "sports_analyst", "sports_bettor", "admin") NOT NULL DEFAULT "regular",
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;',
|
||||
'CREATE TABLE IF NOT EXISTS user_favorites (
|
||||
user_id INT NOT NULL,
|
||||
player_id INT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (user_id, player_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'
|
||||
];
|
||||
|
||||
foreach ($commands as $command) {
|
||||
$pdo->exec($command);
|
||||
}
|
||||
|
||||
echo "Tables created successfully.\n";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Database setup failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
88
index.php
88
index.php
@ -13,65 +13,73 @@
|
||||
<meta name="twitter:image" content="https://project-screens.s3.amazonaws.com/screenshots/34705/app-hero-20251005-234910.png">
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container my-5">
|
||||
<header class="text-center mb-5">
|
||||
<h1 class="display-4">StatTracker</h1>
|
||||
<p class="lead text-secondary">Your personal sports stats dashboard.</p>
|
||||
<div class="search-bar">
|
||||
<input type="text" class="form-control form-control-lg text-center" placeholder="Search for a player...">
|
||||
<header class="mb-5">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">StatTracker</a>
|
||||
<div id="auth-links" class="d-flex">
|
||||
<!-- Auth links will be injected here by main.js -->
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="text-center mt-4">
|
||||
<h1 class="display-4">StatTracker</h1>
|
||||
<p class="lead text-secondary">Your personal sports stats dashboard.</p>
|
||||
<div class="search-bar">
|
||||
<input type="text" id="playerSearch" class="form-control form-control-lg text-center" placeholder="Search for a player...">
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section id="player-comparison" class="mt-5 mb-5">
|
||||
<h2 class="section-title text-center">Head-to-Head Player Comparison</h2>
|
||||
<div class="player-card p-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-5">
|
||||
<label for="player1-search" class="form-label">Player 1</label>
|
||||
<input type="text" id="player1-search" class="form-control player-compare-search" placeholder="Search for Player 1...">
|
||||
<div id="player1-results" class="search-results-popover"></div>
|
||||
</div>
|
||||
<div class="col-md-2 text-center">VS</div>
|
||||
<div class="col-md-5">
|
||||
<label for="player2-search" class="form-label">Player 2</label>
|
||||
<input type="text" id="player2-search" class="form-control player-compare-search" placeholder="Search for Player 2...">
|
||||
<div id="player2-results" class="search-results-popover"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<button id="compare-btn" class="btn btn-primary btn-lg" disabled>Compare Players</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="favorite-players">
|
||||
<h2 class="section-title">My Favorite Players</h2>
|
||||
<div class="row">
|
||||
<?php
|
||||
$favorite_players = [
|
||||
["name" => "LeBron James", "team" => "Los Angeles Lakers", "stats" => "25 PTS, 8 REB, 7 AST", "img" => "https://picsum.photos/seed/player1/150/150"],
|
||||
["name" => "Tom Brady", "team" => "Tampa Bay Buccaneers", "stats" => "300 YDS, 3 TD, 0 INT", "img" => "https://picsum.photos/seed/player4/150/150"],
|
||||
["name" => "Mike Trout", "team" => "Los Angeles Angels", "stats" => "1-3, 1 HR, 2 RBI", "img" => "https://picsum.photos/seed/player5/150/150"],
|
||||
];
|
||||
|
||||
foreach ($favorite_players as $player) {
|
||||
echo '<div class="col-md-4">
|
||||
<div class="player-card text-center">
|
||||
<img src="' . $player["img"] . '" alt="Headshot of ' . $player["name"] . '" class="img-fluid">
|
||||
<div class="player-name">' . $player["name"] . '</div>
|
||||
<div class="player-team">' . $player["team"] . '</div>
|
||||
<div class="player-stats">' . $player["stats"] . '</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
?>
|
||||
<!-- Favorite players will be loaded here -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="popular-players" class="mt-5">
|
||||
<h2 class="section-title">Popular Players</h2>
|
||||
<div class="row">
|
||||
<?php
|
||||
$popular_players = [
|
||||
["name" => "Stephen Curry", "team" => "Golden State Warriors", "stats" => "30 PTS, 5 REB, 9 AST", "img" => "https://picsum.photos/seed/player2/150/150"],
|
||||
["name" => "Patrick Mahomes", "team" => "Kansas City Chiefs", "stats" => "350 YDS, 4 TD, 1 INT", "img" => "https://picsum.photos/seed/player3/150/150"],
|
||||
["name" => "Connor McDavid", "team" => "Edmonton Oilers", "stats" => "1 G, 2 A", "img" => "https://picsum.photos/seed/player6/150/150"],
|
||||
];
|
||||
<!-- Popular players will be loaded here -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
foreach ($popular_players as $player) {
|
||||
echo '<div class="col-md-4">
|
||||
<div class="player-card text-center">
|
||||
<img src="' . $player["img"] . '" alt="Headshot of ' . $player["name"] . '" class="img-fluid">
|
||||
<div class="player-name">' . $player["name"] . '</div>
|
||||
<div class="player-team">' . $player["team"] . '</div>
|
||||
<div class="player-stats">' . $player["stats"] . '</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
?>
|
||||
<section id="search-results" class="mt-5" style="display: none;">
|
||||
<h2 class="section-title">Search Results</h2>
|
||||
<div class="row" id="search-results-container">
|
||||
<!-- Search results will be loaded here -->
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
40
login.php
Normal file
40
login.php
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login - StatTracker</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="player-card p-4">
|
||||
<h1 class="text-center mb-4">Login</h1>
|
||||
<div id="message" class="mb-3"></div>
|
||||
<form id="login-form">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email address</label>
|
||||
<input type="email" class="form-control" id="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Login</button>
|
||||
</form>
|
||||
<div class="text-center mt-3">
|
||||
<a href="register.php">Don't have an account? Register</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/auth.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
45
player.php
Normal file
45
player.php
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Player Details - StatTracker</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container my-5">
|
||||
<header class="mb-5">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">StatTracker</a>
|
||||
<div id="auth-links" class="d-flex">
|
||||
<!-- Auth links are injected by JS -->
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main id="player-detail-container">
|
||||
<!-- Player details will be loaded here by player-detail.js -->
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-light" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer text-center">
|
||||
<p>© <?php echo date("Y"); ?> StatTracker. All Rights Reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/csv-export.js?v=<?php echo time(); ?>"></script>
|
||||
<script src="assets/js/player-detail.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
44
register.php
Normal file
44
register.php
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Register - StatTracker</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="player-card p-4">
|
||||
<h1 class="text-center mb-4">Register</h1>
|
||||
<div id="message" class="mb-3"></div>
|
||||
<form id="register-form">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email address</label>
|
||||
<input type="email" class="form-control" id="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Register</button>
|
||||
</form>
|
||||
<div class="text-center mt-3">
|
||||
<a href="login.php">Already have an account? Login</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/auth.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user