Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e7f6ab776 |
148
api.php
Normal file
148
api.php
Normal file
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
|
||||
echo json_encode(['success' => false, 'error' => 'Authentication required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid request method.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (!$input || !isset($input['action'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid or missing action.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = $input['action'];
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$role = $_SESSION['role'];
|
||||
|
||||
switch ($action) {
|
||||
case 'update_task_status':
|
||||
handle_update_task_status($input, $user_id, $role);
|
||||
break;
|
||||
case 'create_task':
|
||||
handle_create_task($input, $user_id, $role);
|
||||
break;
|
||||
case 'create_user':
|
||||
case 'update_user':
|
||||
case 'delete_user':
|
||||
handle_user_management($action, $input, $role);
|
||||
break;
|
||||
default:
|
||||
echo json_encode(['success' => false, 'error' => 'Unknown action.']);
|
||||
break;
|
||||
}
|
||||
|
||||
function handle_update_task_status($input, $user_id, $role) {
|
||||
if ($role !== 'siswa') {
|
||||
echo json_encode(['success' => false, 'error' => 'Permission denied.']);
|
||||
exit;
|
||||
}
|
||||
if (!isset($input['assignment_id']) || !isset($input['status'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing parameters.']);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE task_assignments SET status = ?, completed_at = ? WHERE id = ? AND assigned_to_user_id = ?");
|
||||
$completed_at = ($input['status'] === 'done') ? date('Y-m-d H:i:s') : null;
|
||||
$stmt->execute([$input['status'], $completed_at, $input['assignment_id'], $user_id]);
|
||||
echo json_encode(['success' => $stmt->rowCount() > 0]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'error' => 'Database error.']);
|
||||
}
|
||||
}
|
||||
|
||||
function handle_create_task($input, $user_id, $role) {
|
||||
if ($role !== 'guru') {
|
||||
echo json_encode(['success' => false, 'error' => 'Permission denied.']);
|
||||
exit;
|
||||
}
|
||||
if (empty($input['title']) || empty($input['student_ids'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Title and at least one student are required.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
$stmt = $pdo->prepare("INSERT INTO tasks (title, description, created_by_user_id, due_date) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$input['title'], $input['description'], $user_id, $input['due_date']]);
|
||||
$task_id = $pdo->lastInsertId();
|
||||
$stmt = $pdo->prepare("INSERT INTO task_assignments (task_id, assigned_to_user_id) VALUES (?, ?)");
|
||||
foreach ($input['student_ids'] as $student_id) {
|
||||
$stmt->execute([$task_id, $student_id]);
|
||||
}
|
||||
$pdo->commit();
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
echo json_encode(['success' => false, 'error' => 'Database error during task creation.']);
|
||||
}
|
||||
}
|
||||
|
||||
function handle_user_management($action, $input, $role) {
|
||||
if ($role !== 'admin') {
|
||||
echo json_encode(['success' => false, 'error' => 'Permission denied.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
try {
|
||||
switch ($action) {
|
||||
case 'create_user':
|
||||
if (empty($input['username']) || empty($input['password']) || empty($input['role'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Username, password, and role are required.']);
|
||||
return;
|
||||
}
|
||||
$hashed_password = password_hash($input['password'], PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$input['username'], $hashed_password, $input['role']]);
|
||||
break;
|
||||
|
||||
case 'update_user':
|
||||
if (empty($input['user_id']) || empty($input['username']) || empty($input['role'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'User ID, username, and role are required.']);
|
||||
return;
|
||||
}
|
||||
if (!empty($input['password'])) {
|
||||
$hashed_password = password_hash($input['password'], PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("UPDATE users SET username = ?, password = ?, role = ? WHERE id = ?");
|
||||
$stmt->execute([$input['username'], $hashed_password, $input['role'], $input['user_id']]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("UPDATE users SET username = ?, role = ? WHERE id = ?");
|
||||
$stmt->execute([$input['username'], $input['role'], $input['user_id']]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete_user':
|
||||
if (empty($input['user_id'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'User ID is required.']);
|
||||
return;
|
||||
}
|
||||
// Prevent admin from deleting themselves
|
||||
if ($input['user_id'] == $_SESSION['user_id']) {
|
||||
echo json_encode(['success' => false, 'error' => 'You cannot delete your own account.']);
|
||||
return;
|
||||
}
|
||||
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
|
||||
$stmt->execute([$input['user_id']]);
|
||||
break;
|
||||
}
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (PDOException $e) {
|
||||
// Check for duplicate username error
|
||||
if ($e->errorInfo[1] == 1062) {
|
||||
echo json_encode(['success' => false, 'error' => 'Username already exists.']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Database error: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
assets/pasted-20251008-084914-8d794eeb.png
Normal file
BIN
assets/pasted-20251008-084914-8d794eeb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 236 KiB |
224
auth.php
Normal file
224
auth.php
Normal file
@ -0,0 +1,224 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
// --- Remember Me Logic ---
|
||||
if (!isset($_SESSION['user_id']) && isset($_COOKIE['remember_me'])) {
|
||||
loginViaCookie();
|
||||
}
|
||||
|
||||
// --- Action Routing ---
|
||||
header('Content-Type: application/json');
|
||||
$action = $_POST['action'] ?? $_GET['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'login':
|
||||
handle_login();
|
||||
break;
|
||||
case 'logout':
|
||||
handle_logout();
|
||||
break;
|
||||
case 'check_session':
|
||||
check_session();
|
||||
break;
|
||||
case 'register':
|
||||
handle_register();
|
||||
break;
|
||||
default:
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid action']);
|
||||
break;
|
||||
}
|
||||
|
||||
function handle_login() {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$remember = isset($_POST['remember']);
|
||||
$demo_users = ['admin', 'guru', 'siswa'];
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Username and password are required.']);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
|
||||
$stmt->execute(['username' => $username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
// For demo users, we don't regenerate the session ID to allow multiple logins.
|
||||
// For regular users, we do it for security.
|
||||
if (!in_array($username, $demo_users)) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
|
||||
$_SESSION['loggedin'] = true;
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['role'] = $user['role'];
|
||||
|
||||
if ($remember) {
|
||||
setRememberMeCookie($user['id']);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Login successful!',
|
||||
'user' => [
|
||||
'username' => $user['username'],
|
||||
'role' => $user['role']
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid username or password.']);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
function handle_register() {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Username dan password tidak boleh kosong.']);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen($password) < 6) {
|
||||
echo json_encode(['success' => false, 'message' => 'Password minimal harus 6 karakter.']);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Username hanya boleh berisi huruf, angka, dan underscore.']);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = :username");
|
||||
$stmt->execute(['username' => $username]);
|
||||
if ($stmt->fetch()) {
|
||||
echo json_encode(['success' => false, 'message' => 'Username sudah digunakan. Silakan pilih yang lain.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("INSERT INTO users (username, password, role) VALUES (:username, :password, 'siswa')");
|
||||
$stmt->execute([
|
||||
'username' => $username,
|
||||
'password' => $hashed_password
|
||||
]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Registrasi berhasil! Anda akan dialihkan ke halaman login.']);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'Terjadi kesalahan pada database.']);
|
||||
}
|
||||
}
|
||||
|
||||
function handle_logout() {
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
clearRememberMeCookie($_SESSION['user_id']);
|
||||
}
|
||||
session_unset();
|
||||
session_destroy();
|
||||
echo json_encode(['success' => true, 'message' => 'Logged out successfully.']);
|
||||
}
|
||||
|
||||
function check_session() {
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'loggedIn' => true,
|
||||
'user' => [
|
||||
'username' => $_SESSION['username'],
|
||||
'role' => $_SESSION['role']
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => true, 'loggedIn' => false]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Remember Me Helper Functions ---
|
||||
|
||||
function setRememberMeCookie($userId) {
|
||||
try {
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$hashed_token = hash('sha256', $token);
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE users SET remember_token = :token WHERE id = :id");
|
||||
$stmt->execute(['token' => $hashed_token, 'id' => $userId]);
|
||||
|
||||
$cookie_value = $userId . ':' . $token;
|
||||
$expiry = time() + (86400 * 30); // 30 days
|
||||
|
||||
setcookie('remember_me', $cookie_value, [
|
||||
'expires' => $expiry,
|
||||
'path' => '/',
|
||||
'domain' => '', // current domain
|
||||
'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax'
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
// Silently fail on cookie setting error
|
||||
}
|
||||
}
|
||||
|
||||
function clearRememberMeCookie($userId) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE users SET remember_token = NULL WHERE id = :id");
|
||||
$stmt->execute(['id' => $userId]);
|
||||
|
||||
if (isset($_COOKIE['remember_me'])) {
|
||||
unset($_COOKIE['remember_me']);
|
||||
setcookie('remember_me', '', time() - 3600, '/');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Silently fail
|
||||
}
|
||||
}
|
||||
|
||||
function loginViaCookie() {
|
||||
$cookie = $_COOKIE['remember_me'] ?? '';
|
||||
if (!$cookie) return;
|
||||
|
||||
$parts = explode(':', $cookie);
|
||||
if (count($parts) !== 2) return;
|
||||
|
||||
list($userId, $token) = $parts;
|
||||
if (empty($userId) || empty($token)) return;
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
|
||||
$stmt->execute(['id' => $userId]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && !empty($user['remember_token'])) {
|
||||
$hashed_token_from_cookie = hash('sha256', $token);
|
||||
if (hash_equals($user['remember_token'], $hashed_token_from_cookie)) {
|
||||
// Log user in
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['loggedin'] = true;
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['role'] = $user['role'];
|
||||
|
||||
// Security: Rotate token
|
||||
setRememberMeCookie($user['id']);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Silently fail
|
||||
}
|
||||
}
|
||||
132
callback-google.php
Normal file
132
callback-google.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
ini_set('display_errors', 0); // Menonaktifkan tampilan error untuk pengguna
|
||||
error_reporting(E_ALL);
|
||||
|
||||
session_start();
|
||||
require_once __DIR__ . '/google-config.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
// Fungsi untuk menampilkan halaman error yang rapi
|
||||
function showErrorPage($message) {
|
||||
http_response_code(500);
|
||||
echo "<div style='font-family: sans-serif; text-align: center; padding: 40px;'>";
|
||||
echo "<h2>Terjadi Kesalahan</h2>";
|
||||
echo "<p>Proses login dengan Google gagal. Silakan coba lagi.</p>";
|
||||
echo "<p style='color: #888; font-size: 0.9em;'>Detail: " . htmlspecialchars($message) . "</p>";
|
||||
echo "<a href='index.php'>Kembali ke Halaman Utama</a>";
|
||||
echo "</div>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1. Pastikan Client ID dan Secret sudah diisi
|
||||
if (GOOGLE_CLIENT_ID === 'MASUKKAN_CLIENT_ID_ANDA_DISINI' || GOOGLE_CLIENT_SECRET === 'MASUKKAN_CLIENT_SECRET_ANDA_DISINI') {
|
||||
showErrorPage('Konfigurasi Google OAuth belum diatur. Silakan hubungi administrator.');
|
||||
}
|
||||
|
||||
// 2. Ambil authorization code dari Google
|
||||
if (!isset($_GET['code'])) {
|
||||
showErrorPage('Authorization code tidak ditemukan.');
|
||||
}
|
||||
$code = $_GET['code'];
|
||||
|
||||
// 3. Tukarkan code dengan access token
|
||||
try {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, 'https://oauth2.googleapis.com/token');
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
|
||||
'client_id' => GOOGLE_CLIENT_ID,
|
||||
'client_secret' => GOOGLE_CLIENT_SECRET,
|
||||
'code' => $code,
|
||||
'redirect_uri' => GOOGLE_REDIRECT_URI,
|
||||
'grant_type' => 'authorization_code'
|
||||
]));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$token_data = json_decode($response, true);
|
||||
if (!isset($token_data['access_token'])) {
|
||||
showErrorPage('Gagal mendapatkan access token dari Google. ' . ($token_data['error_description'] ?? ''));
|
||||
}
|
||||
$access_token = $token_data['access_token'];
|
||||
|
||||
// 4. Gunakan access token untuk mengambil data profil pengguna
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json');
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $access_token]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$profile_response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$profile_data = json_decode($profile_response, true);
|
||||
if (!isset($profile_data['id'])) {
|
||||
showErrorPage('Gagal mengambil data profil dari Google.');
|
||||
}
|
||||
|
||||
// 5. Proses data pengguna (Login atau Register)
|
||||
$google_id = $profile_data['id'];
|
||||
$email = $profile_data['email'];
|
||||
$username = $profile_data['name'];
|
||||
$avatar_url = $profile_data['picture'];
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Cek apakah pengguna sudah ada berdasarkan google_id
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE google_id = :google_id");
|
||||
$stmt->execute(['google_id' => $google_id]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user) {
|
||||
// Pengguna sudah ada, langsung login
|
||||
$user_id = $user['id'];
|
||||
// Mungkin update avatar jika berubah
|
||||
$update_stmt = $pdo->prepare("UPDATE users SET avatar_url = :avatar_url WHERE id = :id");
|
||||
$update_stmt->execute(['avatar_url' => $avatar_url, 'id' => $user_id]);
|
||||
} else {
|
||||
// Pengguna baru, buat akun
|
||||
// Cek dulu apakah email sudah terdaftar (untuk linking akun)
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
|
||||
$stmt->execute(['email' => $email]);
|
||||
$existing_user_by_email = $stmt->fetch();
|
||||
|
||||
if ($existing_user_by_email) {
|
||||
// Email sudah ada, tautkan akun ini dengan google_id
|
||||
$user_id = $existing_user_by_email['id'];
|
||||
$update_stmt = $pdo->prepare("UPDATE users SET google_id = :google_id, avatar_url = :avatar_url WHERE id = :id");
|
||||
$update_stmt->execute(['google_id' => $google_id, 'avatar_url' => $avatar_url, 'id' => $user_id]);
|
||||
} else {
|
||||
// Buat pengguna baru
|
||||
$insert_stmt = $pdo->prepare(
|
||||
"INSERT INTO users (username, email, google_id, avatar_url, role) VALUES (:username, :email, :google_id, :avatar_url, 'siswa')"
|
||||
);
|
||||
$insert_stmt->execute([
|
||||
'username' => $username,
|
||||
'email' => $email,
|
||||
'google_id' => $google_id,
|
||||
'avatar_url' => $avatar_url
|
||||
]);
|
||||
$user_id = $pdo->lastInsertId();
|
||||
}
|
||||
// Ambil data pengguna yang baru dibuat/diupdate
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
|
||||
$stmt->execute(['id' => $user_id]);
|
||||
$user = $stmt->fetch();
|
||||
}
|
||||
|
||||
// 6. Buat sesi login
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['loggedin'] = true;
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['role'] = $user['role'];
|
||||
|
||||
// 7. Redirect ke dashboard
|
||||
header('Location: dashboard.php');
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Tangkap semua jenis error (PDO, cURL, dll)
|
||||
showErrorPage($e->getMessage());
|
||||
}
|
||||
?>
|
||||
533
dashboard.php
Normal file
533
dashboard.php
Normal file
@ -0,0 +1,533 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$username = $_SESSION['username'];
|
||||
$role = $_SESSION['role'];
|
||||
|
||||
$page_title = ucfirst($role) . ' Dashboard';
|
||||
$dashboard_content = '';
|
||||
$students = []; // Initialize students array
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
switch ($role) {
|
||||
case 'siswa':
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT t.id, t.title, t.description, t.due_date, ta.status, ta.id as assignment_id
|
||||
FROM tasks t
|
||||
JOIN task_assignments ta ON t.id = ta.task_id
|
||||
WHERE ta.assigned_to_user_id = ?
|
||||
ORDER BY t.due_date ASC"
|
||||
);
|
||||
$stmt->execute([$user_id]);
|
||||
$tasks = $stmt->fetchAll();
|
||||
|
||||
$dashboard_content = '<h2>Daftar Tugas Anda</h2>';
|
||||
if (empty($tasks)) {
|
||||
$dashboard_content .= '<p>Belum ada tugas untuk Anda. Selamat beristirahat!</p>';
|
||||
} else {
|
||||
$dashboard_content .= '<div class="task-list">';
|
||||
foreach ($tasks as $task) {
|
||||
$status_class = 'status-' . str_replace('_', '-', $task['status']);
|
||||
$due_date = $task['due_date'] ? date('d M Y', strtotime($task['due_date'])) : 'Tidak ada tenggat';
|
||||
$is_done = $task['status'] === 'done';
|
||||
|
||||
$dashboard_content .= '
|
||||
<div class="task-item ' . ($is_done ? 'done' : '') . '" id="task-' . htmlspecialchars($task['assignment_id']) . '">
|
||||
<div class="task-info">
|
||||
<h3>' . htmlspecialchars($task['title']) . '</h3>
|
||||
<p>' . htmlspecialchars($task['description']) . '</p>
|
||||
<small class="due-date">Tenggat: ' . $due_date . '</small>
|
||||
</div>
|
||||
<div class="task-actions">
|
||||
<span class="status-badge ' . $status_class . '">' . ucfirst(str_replace('_', ' ', $task['status'])) . '</span>
|
||||
<button class="complete-btn" data-assignment-id="' . htmlspecialchars($task['assignment_id']) . '" ' . ($is_done ? 'disabled' : '') . '>
|
||||
' . ($is_done ? 'Selesai' : 'Tandai Selesai') . '
|
||||
</button>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
$dashboard_content .= '</div>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'guru':
|
||||
// Fetch tasks created by this teacher
|
||||
$stmt_tasks = $pdo->prepare("SELECT * FROM tasks WHERE created_by_user_id = ? ORDER BY created_at DESC");
|
||||
$stmt_tasks->execute([$user_id]);
|
||||
$created_tasks = $stmt_tasks->fetchAll();
|
||||
|
||||
// Fetch all students for the assignment form
|
||||
$stmt_students = $pdo->prepare("SELECT id, username FROM users WHERE role = 'siswa' ORDER BY username ASC");
|
||||
$stmt_students->execute();
|
||||
$students = $stmt_students->fetchAll();
|
||||
|
||||
$dashboard_content = '
|
||||
<div class="teacher-header">
|
||||
<h2>Manajemen Tugas</h2>
|
||||
<button id="create-task-btn" class="btn btn-primary">Buat Tugas Baru</button>
|
||||
</div>
|
||||
<div class="task-management-table">
|
||||
<h3>Daftar Tugas yang Dibuat</h3>
|
||||
<table id="task-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Judul</th>
|
||||
<th>Tenggat Waktu</th>
|
||||
<th>Dibuat pada</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
if (empty($created_tasks)) {
|
||||
$dashboard_content .= '<tr id="no-tasks-row"><td colspan="4">Anda belum membuat tugas apapun.</td></tr>';
|
||||
} else {
|
||||
foreach ($created_tasks as $task) {
|
||||
$dashboard_content .= '
|
||||
<tr data-task-id="' . $task['id'] . '">
|
||||
<td>' . htmlspecialchars($task['title']) . '</td>
|
||||
<td>' . ($task['due_date'] ? date('d M Y', strtotime($task['due_date'])) : '-') . '</td>
|
||||
<td>' . date('d M Y', strtotime($task['created_at'])) . '</td>
|
||||
<td class="actions">
|
||||
<button class="btn btn-secondary btn-sm">Edit</button>
|
||||
<button class="btn btn-danger btn-sm">Hapus</button>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
$dashboard_content .= '
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
';
|
||||
break;
|
||||
|
||||
case 'admin':
|
||||
// Fetch all users for the admin view
|
||||
$stmt = $pdo->prepare("SELECT id, username, role, created_at FROM users ORDER BY created_at DESC");
|
||||
$stmt->execute();
|
||||
$users = $stmt->fetchAll();
|
||||
|
||||
$dashboard_content = '
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="h3">Manajemen Pengguna</h2>
|
||||
<button class="btn btn-primary" id="create-user-btn">
|
||||
<i class="fas fa-plus me-2"></i>Buat Pengguna Baru
|
||||
</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Username</th>
|
||||
<th>Peran</th>
|
||||
<th>Bergabung pada</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="user-list-tbody">';
|
||||
|
||||
if (empty($users)) {
|
||||
$dashboard_content .= '<tr><td colspan="5" class="text-center">Tidak ada pengguna yang ditemukan.</td></tr>';
|
||||
} else {
|
||||
foreach ($users as $user) {
|
||||
$dashboard_content .= '
|
||||
<tr data-user-id="' . $user['id'] . '">
|
||||
<td>' . htmlspecialchars($user['id']) . '</td>
|
||||
<td>' . htmlspecialchars($user['username']) . '</td>
|
||||
<td><span class="badge bg-secondary">' . htmlspecialchars($user['role']) . '</span></td>
|
||||
<td>' . date('d M Y', strtotime($user['created_at'])) . '</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary edit-user-btn">Edit</button>
|
||||
<button class="btn btn-sm btn-outline-danger delete-user-btn">Hapus</button>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
$dashboard_content .= '
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
break;
|
||||
default:
|
||||
$dashboard_content = '<h2>Selamat Datang!</h2><p>Dashboard Anda sudah siap.</p>';
|
||||
break;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$dashboard_content = '<p>Gagal memuat data. Silakan coba lagi nanti.</p>';
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo htmlspecialchars($page_title); ?> - EduTask</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #4A90E2;
|
||||
--secondary-color: #50E3C2;
|
||||
--danger-color: #e74c3c;
|
||||
--background-color: #F9FAFB;
|
||||
--surface-color: #FFFFFF;
|
||||
--text-color: #333333;
|
||||
--light-gray: #EAEAEA;
|
||||
--success-color: #28a745;
|
||||
--warning-color: #ffc107;
|
||||
--todo-color: #6c757d;
|
||||
}
|
||||
|
||||
body { font-family: 'Open Sans', sans-serif; background-color: var(--background-color); color: var(--text-color); margin: 0; padding: 0; display: flex; flex-direction: column; min-height: 100vh; }
|
||||
.navbar { background-color: var(--surface-color); padding: 1rem 2rem; box-shadow: 0 2px 4px rgba(0,0,0,0.05); display: flex; justify-content: space-between; align-items: center; }
|
||||
.navbar-brand { font-family: 'Poppins', sans-serif; font-size: 1.5rem; font-weight: 700; color: var(--primary-color); text-decoration: none; }
|
||||
.navbar-menu { display: flex; align-items: center; }
|
||||
.user-info { margin-right: 1.5rem; font-weight: 600; text-align: right; }
|
||||
.user-info .role { font-weight: 400; font-size: 0.8rem; color: #777; display: block; }
|
||||
.logout-btn { font-family: 'Poppins', sans-serif; background-color: var(--primary-color); color: white; border: none; padding: 0.6rem 1.2rem; border-radius: 5px; cursor: pointer; text-decoration: none; font-size: 0.9rem; font-weight: 600; transition: background-color 0.3s ease; }
|
||||
.logout-btn:hover { background: linear-gradient(to right, var(--primary-color), var(--secondary-color)); }
|
||||
.main-content { flex: 1; padding: 2rem; }
|
||||
.dashboard-card { background-color: var(--surface-color); border-radius: 8px; padding: 2rem; box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
||||
.dashboard-card h2 { font-family: 'Poppins', sans-serif; color: var(--primary-color); margin-top: 0; }
|
||||
footer { text-align: center; padding: 1rem; background-color: var(--surface-color); font-size: 0.9rem; color: #888; }
|
||||
|
||||
/* General Button Styles */
|
||||
.btn { font-family: 'Poppins', sans-serif; border: none; padding: 0.6rem 1.2rem; border-radius: 5px; cursor: pointer; font-weight: 600; transition: background-color 0.3s ease, box-shadow 0.3s ease; }
|
||||
.btn-primary { background-color: var(--primary-color); color: white; }
|
||||
.btn-primary:hover { background-color: #3a80d2; }
|
||||
.btn-secondary { background-color: #bdc3c7; color: #fff; }
|
||||
.btn-danger { background-color: var(--danger-color); color: white; }
|
||||
.btn-sm { padding: 0.4rem 0.8rem; font-size: 0.85rem; }
|
||||
|
||||
/* Siswa: Task List Styles */
|
||||
.task-list { display: flex; flex-direction: column; gap: 1rem; }
|
||||
.task-item { background-color: #fff; border: 1px solid var(--light-gray); border-radius: 8px; padding: 1rem 1.5rem; display: flex; justify-content: space-between; align-items: center; transition: box-shadow 0.3s ease, border-color 0.3s ease; }
|
||||
.task-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.07); }
|
||||
.task-item.done { background-color: #f1fef4; border-left: 5px solid var(--success-color); }
|
||||
.task-item.done h3 { text-decoration: line-through; color: #888; }
|
||||
.task-info h3 { margin: 0 0 0.25rem 0; font-family: 'Poppins', sans-serif; }
|
||||
.task-info p { margin: 0 0 0.5rem 0; font-size: 0.95rem; color: #555; }
|
||||
.task-info .due-date { font-size: 0.8rem; color: #888; }
|
||||
.task-actions { display: flex; align-items: center; gap: 1rem; }
|
||||
.status-badge { padding: 0.25rem 0.6rem; border-radius: 12px; font-size: 0.8rem; font-weight: 600; color: white; }
|
||||
.status-todo { background-color: var(--todo-color); }
|
||||
.status-in-progress { background-color: var(--warning-color); }
|
||||
.status-done { background-color: var(--success-color); }
|
||||
.complete-btn { background-color: var(--success-color); color: white; border: none; padding: 0.5rem 1rem; border-radius: 5px; cursor: pointer; font-weight: 600; transition: background-color 0.3s ease; }
|
||||
.complete-btn:disabled { background-color: #a9d6b4; cursor: not-allowed; }
|
||||
|
||||
/* Guru: Task Management Styles */
|
||||
.teacher-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
|
||||
.task-management-table table { width: 100%; border-collapse: collapse; }
|
||||
.task-management-table th, .task-management-table td { padding: 0.8rem 1rem; text-align: left; border-bottom: 1px solid var(--light-gray); }
|
||||
.task-management-table th { font-family: 'Poppins', sans-serif; background-color: #f8f9fa; }
|
||||
.task-management-table .actions { display: flex; gap: 0.5rem; }
|
||||
|
||||
/* Modal Styles */
|
||||
.modal-backdrop { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 100; display: none; justify-content: center; align-items: center; }
|
||||
.modal-content { background-color: var(--surface-color); border-radius: 8px; padding: 2rem; width: 90%; max-width: 600px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); position: relative; }
|
||||
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
|
||||
.modal-header h3 { margin: 0; font-family: 'Poppins', sans-serif; }
|
||||
.close-btn { font-size: 1.5rem; font-weight: bold; color: #aaa; cursor: pointer; border: none; background: none; }
|
||||
.form-group { margin-bottom: 1rem; }
|
||||
.form-group label { display: block; margin-bottom: 0.5rem; font-weight: 600; }
|
||||
.form-group input, .form-group textarea { width: 100%; padding: 0.75rem; border: 1px solid var(--light-gray); border-radius: 5px; font-size: 1rem; }
|
||||
.student-list { max-height: 150px; overflow-y: auto; border: 1px solid var(--light-gray); padding: 0.5rem; border-radius: 5px; }
|
||||
.student-list label { display: block; padding: 0.5rem; }
|
||||
.student-list label:hover { background-color: #f8f9fa; }
|
||||
.form-actions { text-align: right; margin-top: 1.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar">
|
||||
<a href="dashboard.php" class="navbar-brand">EduTask</a>
|
||||
<div class="navbar-menu">
|
||||
<div class="user-info">
|
||||
Halo, <?php echo htmlspecialchars($username); ?>!
|
||||
<span class="role">(<?php echo htmlspecialchars($role); ?>)</span>
|
||||
</div>
|
||||
<a href="auth.php?action=logout" class="logout-btn">Logout</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="dashboard-card">
|
||||
<?php echo $dashboard_content; ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php if ($role === 'guru'): ?>
|
||||
<div id="create-task-modal" class="modal-backdrop">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>Buat Tugas Baru</h3>
|
||||
<button class="close-btn">×</button>
|
||||
</div>
|
||||
<form id="create-task-form">
|
||||
<div class="form-group">
|
||||
<label for="task-title">Judul Tugas</label>
|
||||
<input type="text" id="task-title" name="title" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="task-description">Deskripsi</label>
|
||||
<textarea id="task-description" name="description" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="task-due-date">Tenggat Waktu</label>
|
||||
<input type="date" id="task-due-date" name="due_date">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Tugaskan kepada Siswa</label>
|
||||
<div class="student-list">
|
||||
<?php if (empty($students)): ?>
|
||||
<p>Tidak ada siswa yang ditemukan.</p>
|
||||
<?php else: ?>
|
||||
<label><input type="checkbox" id="select-all-students"> Pilih Semua</label>
|
||||
<?php foreach ($students as $student): ?>
|
||||
<label>
|
||||
<input type="checkbox" name="student_ids[]" value="<?php echo $student['id']; ?>">
|
||||
<?php echo htmlspecialchars($student['username']); ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Simpan Tugas</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($role === 'admin'): ?>
|
||||
<div id="user-modal" class="modal-backdrop">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 id="user-modal-title">Buat Pengguna Baru</h3>
|
||||
<button class="close-btn">×</button>
|
||||
</div>
|
||||
<form id="user-form">
|
||||
<input type="hidden" id="user-id" name="user_id">
|
||||
<div class="form-group">
|
||||
<label for="user-username">Username</label>
|
||||
<input type="text" id="user-username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user-password">Password</label>
|
||||
<input type="password" id="user-password" name="password">
|
||||
<small>Kosongkan jika tidak ingin mengubah password.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user-role">Peran</label>
|
||||
<select id="user-role" name="role" required>
|
||||
<option value="siswa">Siswa</option>
|
||||
<option value="guru">Guru</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<footer>
|
||||
<p>© <?php echo date('Y'); ?> EduTask. Hak cipta dilindungi.</p>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const role = '<?php echo $role; ?>';
|
||||
|
||||
// --- Generic API call function ---
|
||||
function callApi(body) {
|
||||
return fetch('api.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body),
|
||||
}).then(response => response.json());
|
||||
}
|
||||
|
||||
// --- Logic for Siswa ---
|
||||
if (role === 'siswa') {
|
||||
const taskList = document.querySelector('.task-list');
|
||||
if (taskList) {
|
||||
taskList.addEventListener('click', function(e) {
|
||||
if (e.target && e.target.classList.contains('complete-btn')) {
|
||||
const button = e.target;
|
||||
const assignmentId = button.getAttribute('data-assignment-id');
|
||||
if (button.disabled) return;
|
||||
|
||||
callApi({ action: 'update_task_status', assignment_id: assignmentId, status: 'done' })
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const taskItem = document.getElementById('task-' + assignmentId);
|
||||
taskItem.classList.add('done');
|
||||
button.textContent = 'Selesai';
|
||||
button.disabled = true;
|
||||
const statusBadge = taskItem.querySelector('.status-badge');
|
||||
statusBadge.textContent = 'Done';
|
||||
statusBadge.classList.remove('status-todo', 'status-in-progress');
|
||||
statusBadge.classList.add('status-done');
|
||||
} else {
|
||||
alert('Gagal memperbarui status tugas: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
}).catch(error => console.error('Error:', error));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Logic for Guru ---
|
||||
if (role === 'guru') {
|
||||
const modal = document.getElementById('create-task-modal');
|
||||
const createTaskBtn = document.getElementById('create-task-btn');
|
||||
const closeBtn = modal ? modal.querySelector('.close-btn') : null;
|
||||
const form = document.getElementById('create-task-form');
|
||||
const selectAllCheckbox = document.getElementById('select-all-students');
|
||||
|
||||
if (createTaskBtn) createTaskBtn.addEventListener('click', () => { modal.style.display = 'flex'; });
|
||||
if (closeBtn) closeBtn.addEventListener('click', () => { modal.style.display = 'none'; });
|
||||
window.addEventListener('click', (e) => { if (e.target === modal) modal.style.display = 'none'; });
|
||||
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.addEventListener('change', function() {
|
||||
form.querySelectorAll('input[name="student_ids[]"]').forEach(cb => { cb.checked = this.checked; });
|
||||
});
|
||||
}
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(form);
|
||||
const student_ids = formData.getAll('student_ids[]');
|
||||
if (student_ids.length === 0) {
|
||||
alert('Pilih minimal satu siswa untuk ditugaskan.');
|
||||
return;
|
||||
}
|
||||
|
||||
callApi({
|
||||
action: 'create_task',
|
||||
title: formData.get('title'),
|
||||
description: formData.get('description'),
|
||||
due_date: formData.get('due_date'),
|
||||
student_ids: student_ids
|
||||
}).then(data => {
|
||||
if (data.success) {
|
||||
alert('Tugas berhasil dibuat!');
|
||||
modal.style.display = 'none';
|
||||
form.reset();
|
||||
location.reload(); // Simple reload to show the new task
|
||||
} else {
|
||||
alert('Gagal membuat tugas: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
}).catch(error => console.error('Error:', error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Logic for Admin ---
|
||||
if (role === 'admin') {
|
||||
const userModal = document.getElementById('user-modal');
|
||||
const createUserBtn = document.getElementById('create-user-btn');
|
||||
const userCloseBtn = userModal ? userModal.querySelector('.close-btn') : null;
|
||||
const userForm = document.getElementById('user-form');
|
||||
const userListTbody = document.getElementById('user-list-tbody');
|
||||
|
||||
const openUserModal = (user = null) => {
|
||||
userForm.reset();
|
||||
const modalTitle = document.getElementById('user-modal-title');
|
||||
const userIdInput = document.getElementById('user-id');
|
||||
const passwordInput = document.getElementById('user-password');
|
||||
if (user) {
|
||||
modalTitle.textContent = 'Edit Pengguna';
|
||||
userIdInput.value = user.id;
|
||||
document.getElementById('user-username').value = user.username;
|
||||
document.getElementById('user-role').value = user.role;
|
||||
passwordInput.required = false;
|
||||
} else {
|
||||
modalTitle.textContent = 'Buat Pengguna Baru';
|
||||
userIdInput.value = '';
|
||||
passwordInput.required = true;
|
||||
}
|
||||
userModal.style.display = 'flex';
|
||||
};
|
||||
|
||||
const closeUserModal = () => {
|
||||
userModal.style.display = 'none';
|
||||
};
|
||||
|
||||
if (createUserBtn) createUserBtn.addEventListener('click', () => openUserModal());
|
||||
if (userCloseBtn) userCloseBtn.addEventListener('click', closeUserModal);
|
||||
window.addEventListener('click', (e) => { if (e.target === userModal) closeUserModal(); });
|
||||
|
||||
userListTbody.addEventListener('click', e => {
|
||||
const target = e.target;
|
||||
const userRow = target.closest('tr');
|
||||
const userId = userRow.dataset.userId;
|
||||
|
||||
if (target.classList.contains('edit-user-btn')) {
|
||||
const username = userRow.cells[1].textContent;
|
||||
const role = userRow.cells[2].textContent;
|
||||
openUserModal({ id: userId, username, role });
|
||||
}
|
||||
|
||||
if (target.classList.contains('delete-user-btn')) {
|
||||
if (confirm(`Apakah Anda yakin ingin menghapus pengguna dengan ID ${userId}?`)) {
|
||||
callApi({ action: 'delete_user', user_id: userId }).then(data => {
|
||||
if (data.success) {
|
||||
alert('Pengguna berhasil dihapus.');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Gagal menghapus pengguna: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
}).catch(error => console.error('Error:', error));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
userForm.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(userForm);
|
||||
const userId = formData.get('user_id');
|
||||
const action = userId ? 'update_user' : 'create_user';
|
||||
|
||||
let body = { action };
|
||||
for (let [key, value] of formData.entries()) {
|
||||
body[key] = value;
|
||||
}
|
||||
|
||||
callApi(body).then(data => {
|
||||
if (data.success) {
|
||||
alert(`Pengguna berhasil ${userId ? 'diperbarui' : 'dibuat'}.`);
|
||||
closeUserModal();
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Gagal: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
}).catch(error => console.error('Error:', error));
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
109
db/setup.php
Normal file
109
db/setup.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
try {
|
||||
// 1. Connect without DB to create it
|
||||
$pdo_admin = new PDO('mysql:host='.DB_HOST.';charset=utf8mb4', DB_USER, DB_PASS);
|
||||
$pdo_admin->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo_admin->exec("CREATE DATABASE IF NOT EXISTS `".DB_NAME."`");
|
||||
$pdo_admin = null; // close connection
|
||||
|
||||
// 2. Now use the standard connection from config.php
|
||||
$pdo = db();
|
||||
|
||||
// Create users table
|
||||
$pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role ENUM('admin', 'guru', 'siswa') NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)"
|
||||
);
|
||||
echo "Table 'users' created successfully or already exists.\n";
|
||||
|
||||
// --- Add new columns to users table (idempotent) ---
|
||||
$new_columns = [
|
||||
'email' => "ADD COLUMN email VARCHAR(255) NULL UNIQUE AFTER username",
|
||||
'google_id' => "ADD COLUMN google_id VARCHAR(255) NULL UNIQUE AFTER email",
|
||||
'avatar_url' => "ADD COLUMN avatar_url VARCHAR(255) NULL AFTER google_id",
|
||||
'remember_token' => "ADD COLUMN remember_token VARCHAR(255) NULL DEFAULT NULL AFTER password"
|
||||
];
|
||||
|
||||
$check_col_stmt = $pdo->prepare("SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = :db_name AND table_name = 'users' AND column_name = :col_name");
|
||||
|
||||
foreach ($new_columns as $column_name => $alter_statement) {
|
||||
$check_col_stmt->execute([':db_name' => DB_NAME, ':col_name' => $column_name]);
|
||||
if ($check_col_stmt->fetchColumn() === false) {
|
||||
$pdo->exec("ALTER TABLE users " . $alter_statement);
|
||||
echo "Column '{$column_name}' added to 'users' table.\n";
|
||||
} else {
|
||||
echo "Column '{$column_name}' already exists in 'users' table. Skipping.\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Make password nullable for Google-only users
|
||||
$pdo->exec("ALTER TABLE users MODIFY password VARCHAR(255) NULL");
|
||||
|
||||
|
||||
// Create tasks table
|
||||
$pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS tasks (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
created_by_user_id INT NOT NULL,
|
||||
due_date DATETIME NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)"
|
||||
);
|
||||
echo "Table 'tasks' created successfully or already exists.\n";
|
||||
|
||||
// Create task_assignments table
|
||||
$pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS task_assignments (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
task_id INT NOT NULL,
|
||||
assigned_to_user_id INT NOT NULL,
|
||||
status ENUM('todo', 'in_progress', 'done') NOT NULL DEFAULT 'todo',
|
||||
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
completed_at DATETIME NULL,
|
||||
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (assigned_to_user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)"
|
||||
);
|
||||
echo "Table 'task_assignments' created successfully or already exists.\n";
|
||||
|
||||
|
||||
// Insert demo users
|
||||
$users = [
|
||||
['username' => 'admin', 'password' => 'admin123', 'role' => 'admin'],
|
||||
['username' => 'guru', 'password' => 'guru123', 'role' => 'guru'],
|
||||
['username' => 'siswa', 'password' => 'siswa123', 'role' => 'siswa']
|
||||
];
|
||||
|
||||
$insert_stmt = $pdo->prepare("INSERT INTO users (username, password, role) VALUES (:username, :password, :role)");
|
||||
$check_stmt = $pdo->prepare("SELECT id FROM users WHERE username = :username");
|
||||
|
||||
foreach ($users as $user) {
|
||||
$check_stmt->execute(['username' => $user['username']]);
|
||||
if ($check_stmt->fetch()) {
|
||||
echo "User '{$user['username']}' already exists. Skipping.\n";
|
||||
} else {
|
||||
$hashed_password = password_hash($user['password'], PASSWORD_DEFAULT);
|
||||
$insert_stmt->execute([
|
||||
':username' => $user['username'],
|
||||
':password' => $hashed_password,
|
||||
':role' => $user['role']
|
||||
]);
|
||||
echo "User '{$user['username']}' inserted.\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "Database setup completed successfully!\n";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Database error: " . $e->getMessage());
|
||||
}
|
||||
19
google-config.php
Normal file
19
google-config.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
// Konfigurasi untuk Google OAuth 2.0
|
||||
|
||||
// 1. Ganti dengan Client ID yang Anda dapatkan dari Google Cloud Console.
|
||||
// Ini adalah informasi publik.
|
||||
define('GOOGLE_CLIENT_ID', 'MASUKKAN_CLIENT_ID_ANDA_DISINI');
|
||||
|
||||
// 2. Ganti dengan Client Secret yang Anda dapatkan.
|
||||
// JAGA KERAHASIAAN INI. Sebaiknya disimpan di environment variable.
|
||||
define('GOOGLE_CLIENT_SECRET', 'MASUKKAN_CLIENT_SECRET_ANDA_DISINI');
|
||||
|
||||
// 3. Ini adalah URL callback yang harus Anda daftarkan di Google Cloud Console.
|
||||
// URL ini harus sama persis dengan yang ada di konsol.
|
||||
// Pastikan untuk menggunakan https jika situs Anda sudah live.
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http";
|
||||
$host = $_SERVER['HTTP_HOST'];
|
||||
define('GOOGLE_REDIRECT_URI', $protocol . '://' . $host . '/callback-google.php');
|
||||
|
||||
?>
|
||||
745
index.php
745
index.php
@ -1,150 +1,605 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<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=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>EduTask – To-Do List Siswa Pintar</title>
|
||||
<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;700&family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #4A90E2;
|
||||
--secondary-color: #50E3C2;
|
||||
--background-color: #F9FAFB;
|
||||
--surface-color: #FFFFFF;
|
||||
--text-color: #333333;
|
||||
--heading-font: 'Poppins', sans-serif;
|
||||
--body-font: 'Open Sans', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--body-font);
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--surface-color);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
padding: 1rem 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: var(--heading-font);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-buttons .btn, .user-menu .btn {
|
||||
font-family: var(--heading-font);
|
||||
text-decoration: none;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
color: var(--primary-color);
|
||||
background-color: transparent;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-register {
|
||||
color: #fff;
|
||||
background-image: linear-gradient(to right, var(--primary-color), var(--secondary-color));
|
||||
background-size: 200% auto;
|
||||
}
|
||||
|
||||
.btn-register:hover {
|
||||
background-position: right center;
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 6rem 0;
|
||||
background-image: linear-gradient(120deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-family: var(--heading-font);
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.2rem;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 2rem auto;
|
||||
}
|
||||
|
||||
.hero .btn-cta {
|
||||
font-family: var(--heading-font);
|
||||
font-size: 1.1rem;
|
||||
color: var(--primary-color);
|
||||
background-color: var(--surface-color);
|
||||
padding: 0.8rem 2.5rem;
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.features {
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.features h2 {
|
||||
font-family: var(--heading-font);
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background-color: var(--surface-color);
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.07);
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
font-family: var(--heading-font);
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 2rem 0;
|
||||
background-color: #eef2f5;
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1001;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--surface-color);
|
||||
margin: auto;
|
||||
padding: 2rem 2.5rem;
|
||||
border-radius: 12px;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
animation: slide-down 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slide-down {
|
||||
from {
|
||||
transform: translateY(-30px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
font-family: var(--heading-font);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
color: #aaa;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-submit-btn {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background-image: linear-gradient(to right, var(--primary-color), var(--secondary-color));
|
||||
background-size: 200%;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-position 0.3s;
|
||||
}
|
||||
|
||||
.form-submit-btn:hover {
|
||||
background-position: right center;
|
||||
}
|
||||
|
||||
.form-message {
|
||||
margin-top: 1rem;
|
||||
padding: 0.8rem;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
.form-message.success {
|
||||
background-color: #e0f8e9;
|
||||
color: #2d6a4f;
|
||||
}
|
||||
.form-message.error {
|
||||
background-color: #fde0e0;
|
||||
color: #9e2a2b;
|
||||
}
|
||||
|
||||
/* User Menu */
|
||||
.user-menu {
|
||||
display: none; /* Hidden by default */
|
||||
align-items: center;
|
||||
}
|
||||
.user-menu span {
|
||||
margin-right: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-logout {
|
||||
background-color: #fde0e0;
|
||||
color: #9e2a2b;
|
||||
}
|
||||
.btn-demo {
|
||||
background-color: #eef2f5;
|
||||
border: 1px solid #ddd;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-family: var(--body-font);
|
||||
font-weight: 600;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.btn-demo:hover {
|
||||
background-color: #e1e8ed;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
|
||||
<header>
|
||||
<div class="container">
|
||||
<nav class="nav-bar">
|
||||
<a href="/" class="logo">EduTask</a>
|
||||
<div class="nav-buttons">
|
||||
<button class="btn btn-login">Login</button>
|
||||
<button class="btn btn-register">Register</button>
|
||||
</div>
|
||||
<div class="user-menu">
|
||||
<span id="user-greeting"></span>
|
||||
<button class="btn btn-logout">Logout</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<h1>Manajemen Tugas Sekolah Jadi Mudah</h1>
|
||||
<p>Platform modern untuk guru dan siswa mengelola tugas, deadline, dan penilaian secara efisien. Fokus belajar, bukan administrasi.</p>
|
||||
<a href="#register" class="btn btn-cta">Mulai Sekarang Gratis</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="features">
|
||||
<div class="container">
|
||||
<h2>Fitur Unggulan</h2>
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<h3>Untuk Guru</h3>
|
||||
<p>Buat, bagikan, dan nilai tugas dengan mudah. Pantau progres siswa dalam satu kalender terintegrasi.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>Untuk Siswa</h3>
|
||||
<p>Lihat semua tugas dalam satu tempat, kumpulkan pekerjaan, dan dapatkan notifikasi deadline. Jangan terlambat lagi!</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>Untuk Admin</h3>
|
||||
<p>Kelola pengguna, pantau statistik, dan pastikan sistem berjalan lancar untuk semua sekolah.</p> </div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2025 EduTask. Dibuat untuk Pendidikan yang Lebih Baik.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Login Modal -->
|
||||
<div id="login-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Login</h2>
|
||||
<span class="close-btn">×</span>
|
||||
</div>
|
||||
<form id="login-form">
|
||||
<div class="form-group">
|
||||
<label for="login-username">Username</label>
|
||||
<input type="text" id="login-username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="login-password">Password</label>
|
||||
<input type="password" id="login-password" name="password" required>
|
||||
</div>
|
||||
<div class="form-group-extra" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
|
||||
<label style="display: flex; align-items: center; font-size: 0.9rem; cursor: pointer;">
|
||||
<input type="checkbox" name="remember" id="remember-me" style="margin-right: 0.5rem;">
|
||||
Remember Me
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="form-submit-btn">Login</button>
|
||||
<div id="login-message" class="form-message"></div>
|
||||
</form>
|
||||
<div class="demo-logins" style="text-align: center; margin-top: 1.5rem;">
|
||||
<p style="margin-bottom: 1rem; font-weight: 600; color: #555;">Atau coba akun demo:</p>
|
||||
<div style="display: flex; justify-content: center; gap: 0.5rem; flex-wrap: wrap;">
|
||||
<button class="btn-demo" data-role="admin">Admin</button>
|
||||
<button class="btn-demo" data-role="guru">Guru</button>
|
||||
<button class="btn-demo" data-role="siswa">Siswa</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="social-login" style="text-align: center; margin-top: 1.5rem; border-top: 1px solid #eee; padding-top: 1.5rem;">
|
||||
<button id="google-login-btn" style="background-color: #DB4437; color: white; border: none; padding: 10px 15px; border-radius: 8px; font-weight: 600; cursor: pointer; width: 100%;">
|
||||
<svg style="width: 18px; height: 18px; vertical-align: middle; margin-right: 8px;" viewBox="0 0 48 48"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"></path><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"></path><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"></path><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"></path><path fill="none" d="M0 0h48v48H0z"></path></svg>
|
||||
Login dengan Google
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
|
||||
<!-- Register Modal -->
|
||||
<div id="register-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Daftar Akun Baru</h2>
|
||||
<span class="close-btn">×</span>
|
||||
</div>
|
||||
<form id="register-form">
|
||||
<div class="form-group">
|
||||
<label for="register-username">Username</label>
|
||||
<input type="text" id="register-username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="register-password">Password</label>
|
||||
<input type="password" id="register-password" name="password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="register-password-confirm">Konfirmasi Password</label>
|
||||
<input type="password" id="register-password-confirm" name="password_confirm" required>
|
||||
</div>
|
||||
<button type="submit" class="form-submit-btn">Register</button>
|
||||
<div id="register-message" class="form-message"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const loginModal = document.getElementById('login-modal');
|
||||
const registerModal = document.getElementById('register-modal');
|
||||
|
||||
const btnLogin = document.querySelector('.btn-login');
|
||||
const btnRegister = document.querySelector('.btn-register');
|
||||
const btnCta = document.querySelector('.btn-cta');
|
||||
|
||||
const closeBtns = document.querySelectorAll('.close-btn');
|
||||
|
||||
const navButtons = document.querySelector('.nav-buttons');
|
||||
const userMenu = document.querySelector('.user-menu');
|
||||
const userGreeting = document.getElementById('user-greeting');
|
||||
const btnLogout = document.querySelector('.btn-logout');
|
||||
|
||||
const loginForm = document.getElementById('login-form');
|
||||
const loginMessage = document.getElementById('login-message');
|
||||
const registerForm = document.getElementById('register-form');
|
||||
const registerMessage = document.getElementById('register-message');
|
||||
const loginUsernameInput = document.getElementById('login-username');
|
||||
const loginPasswordInput = document.getElementById('login-password');
|
||||
|
||||
// --- Modal Control ---
|
||||
function openModal(modal) {
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeModal(modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
|
||||
if (btnLogin) btnLogin.addEventListener('click', () => openModal(loginModal));
|
||||
if (btnRegister) btnRegister.addEventListener('click', () => openModal(registerModal));
|
||||
if (btnCta) btnCta.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
openModal(registerModal);
|
||||
});
|
||||
|
||||
closeBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
closeModal(loginModal);
|
||||
closeModal(registerModal);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('click', (e) => {
|
||||
if (e.target === loginModal) closeModal(loginModal);
|
||||
if (e.target === registerModal) closeModal(registerModal);
|
||||
});
|
||||
|
||||
// --- Demo Login ---
|
||||
function handleDemoLogin(role) {
|
||||
const credentials = {
|
||||
admin: { user: 'admin', pass: 'admin123' },
|
||||
guru: { user: 'guru', pass: 'guru123' },
|
||||
siswa: { user: 'siswa', pass: 'siswa123' }
|
||||
};
|
||||
|
||||
if (credentials[role]) {
|
||||
loginUsernameInput.value = credentials[role].user;
|
||||
loginPasswordInput.value = credentials[role].pass;
|
||||
// A slight delay to allow UI to update if needed, then submit
|
||||
setTimeout(() => {
|
||||
loginForm.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll('.btn-demo').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const role = e.target.dataset.role;
|
||||
handleDemoLogin(role);
|
||||
});
|
||||
});
|
||||
|
||||
// --- Authentication Logic ---
|
||||
function updateUIForLoggedOutUser() {
|
||||
if (navButtons) navButtons.style.display = 'block';
|
||||
if (userMenu) userMenu.style.display = 'none';
|
||||
}
|
||||
|
||||
async function checkSession() {
|
||||
try {
|
||||
const response = await fetch('auth.php?action=check_session');
|
||||
const data = await response.json();
|
||||
if (data.success && data.loggedIn) {
|
||||
window.location.href = 'dashboard.php';
|
||||
} else {
|
||||
updateUIForLoggedOutUser();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Session check failed:', error);
|
||||
updateUIForLoggedOutUser();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogin(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(loginForm);
|
||||
formData.append('action', 'login');
|
||||
|
||||
try {
|
||||
const response = await fetch('auth.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
window.location.href = 'dashboard.php';
|
||||
} else {
|
||||
loginMessage.textContent = data.message;
|
||||
loginMessage.className = 'form-message error';
|
||||
loginMessage.style.display = 'block';
|
||||
}
|
||||
} catch (error) {
|
||||
loginMessage.textContent = 'Terjadi kesalahan. Silakan coba lagi.';
|
||||
loginMessage.className = 'form-message error';
|
||||
loginMessage.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRegister(e) {
|
||||
e.preventDefault();
|
||||
const password = document.getElementById('register-password').value;
|
||||
const passwordConfirm = document.getElementById('register-password-confirm').value;
|
||||
|
||||
if (password !== passwordConfirm) {
|
||||
registerMessage.textContent = 'Password dan konfirmasi password tidak cocok.';
|
||||
registerMessage.className = 'form-message error';
|
||||
registerMessage.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(registerForm);
|
||||
formData.append('action', 'register');
|
||||
|
||||
try {
|
||||
const response = await fetch('auth.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
registerMessage.textContent = data.message;
|
||||
registerMessage.className = 'form-message success';
|
||||
registerMessage.style.display = 'block';
|
||||
registerForm.reset();
|
||||
setTimeout(() => {
|
||||
closeModal(registerModal);
|
||||
openModal(loginModal);
|
||||
}, 2000);
|
||||
} else {
|
||||
registerMessage.textContent = data.message;
|
||||
registerMessage.className = 'form-message error';
|
||||
registerMessage.style.display = 'block';
|
||||
}
|
||||
} catch (error) {
|
||||
registerMessage.textContent = 'Terjadi kesalahan. Silakan coba lagi.';
|
||||
registerMessage.className = 'form-message error';
|
||||
registerMessage.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
try {
|
||||
await fetch('auth.php?action=logout');
|
||||
window.location.href = 'index.php';
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (loginForm) loginForm.addEventListener('submit', handleLogin);
|
||||
if (registerForm) registerForm.addEventListener('submit', handleRegister);
|
||||
if (btnLogout) btnLogout.addEventListener('click', handleLogout);
|
||||
|
||||
const googleLoginBtn = document.getElementById('google-login-btn');
|
||||
if(googleLoginBtn) {
|
||||
googleLoginBtn.addEventListener('click', () => {
|
||||
window.location.href = 'login-google.php';
|
||||
});
|
||||
}
|
||||
|
||||
checkSession();
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
23
login-google.php
Normal file
23
login-google.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/google-config.php';
|
||||
|
||||
// Hapus sesi sebelumnya jika ada, untuk memastikan login baru
|
||||
session_start();
|
||||
session_unset();
|
||||
|
||||
// Parameter untuk otentikasi Google
|
||||
$params = [
|
||||
'response_type' => 'code',
|
||||
'client_id' => GOOGLE_CLIENT_ID,
|
||||
'redirect_uri' => GOOGLE_REDIRECT_URI,
|
||||
'scope' => 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
|
||||
'access_type' => 'offline',
|
||||
'prompt' => 'select_account' // Memaksa pengguna memilih akun setiap kali
|
||||
];
|
||||
|
||||
$auth_url = 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query($params);
|
||||
|
||||
// Redirect ke halaman otentikasi Google
|
||||
header('Location: ' . $auth_url);
|
||||
exit();
|
||||
?>
|
||||
Loading…
x
Reference in New Issue
Block a user