Compare commits

...

1 Commits

Author SHA1 Message Date
Flatlogic Bot
0e7f6ab776 Edutask V1 2025-10-08 08:54:03 +00:00
9 changed files with 1788 additions and 145 deletions

148
api.php Normal file
View 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()]);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

224
auth.php Normal file
View 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
View 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
View 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">&times;</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">&times;</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>&copy; <?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
View 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
View 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
View File

@ -1,150 +1,605 @@
<?php <!DOCTYPE html>
declare(strict_types=1); <html lang="id">
@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">
<head> <head>
<meta charset="utf-8" /> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>New Style</title> <title>EduTask To-Do List Siswa Pintar</title>
<?php <link rel="preconnect" href="https://fonts.googleapis.com">
// Read project preview data from environment <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ''; <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; <style>
?> :root {
<?php if ($projectDescription): ?> --primary-color: #4A90E2;
<!-- Meta description --> --secondary-color: #50E3C2;
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' /> --background-color: #F9FAFB;
<!-- Open Graph meta tags --> --surface-color: #FFFFFF;
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" /> --text-color: #333333;
<!-- Twitter meta tags --> --heading-font: 'Poppins', sans-serif;
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" /> --body-font: 'Open Sans', sans-serif;
<?php endif; ?> }
<?php if ($projectImageUrl): ?>
<!-- Open Graph image --> body {
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" /> margin: 0;
<!-- Twitter image --> font-family: var(--body-font);
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" /> background-color: var(--background-color);
<?php endif; ?> color: var(--text-color);
<link rel="preconnect" href="https://fonts.googleapis.com"> line-height: 1.6;
<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> .container {
:root { width: 90%;
--bg-color-start: #6a11cb; max-width: 1200px;
--bg-color-end: #2575fc; margin: 0 auto;
--text-color: #ffffff; }
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1); header {
} background-color: var(--surface-color);
body { box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin: 0; padding: 1rem 0;
font-family: 'Inter', sans-serif; position: sticky;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); top: 0;
color: var(--text-color); z-index: 1000;
display: flex; }
justify-content: center;
align-items: center; .nav-bar {
min-height: 100vh; display: flex;
text-align: center; justify-content: space-between;
overflow: hidden; align-items: center;
position: relative; }
}
body::before { .logo {
content: ''; font-family: var(--heading-font);
position: absolute; font-size: 1.5rem;
top: 0; font-weight: 700;
left: 0; color: var(--primary-color);
width: 100%; text-decoration: none;
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; .nav-buttons .btn, .user-menu .btn {
z-index: -1; font-family: var(--heading-font);
} text-decoration: none;
@keyframes bg-pan { padding: 0.6rem 1.2rem;
0% { background-position: 0% 0%; } border-radius: 8px;
100% { background-position: 100% 100%; } font-weight: 600;
} transition: all 0.3s ease;
main { cursor: pointer;
padding: 2rem; border: none;
} }
.card {
background: var(--card-bg-color); .btn-login {
border: 1px solid var(--card-border-color); color: var(--primary-color);
border-radius: 16px; background-color: transparent;
padding: 2rem; margin-right: 0.5rem;
backdrop-filter: blur(20px); }
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1); .btn-register {
} color: #fff;
.loader { background-image: linear-gradient(to right, var(--primary-color), var(--secondary-color));
margin: 1.25rem auto 1.25rem; background-size: 200% auto;
width: 48px; }
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25); .btn-register:hover {
border-top-color: #fff; background-position: right center;
border-radius: 50%; }
animation: spin 1s linear infinite;
} .hero {
@keyframes spin { text-align: center;
from { transform: rotate(0deg); } padding: 6rem 0;
to { transform: rotate(360deg); } background-image: linear-gradient(120deg, var(--primary-color) 0%, var(--secondary-color) 100%);
} color: white;
.hint { }
opacity: 0.9;
} .hero h1 {
.sr-only { font-family: var(--heading-font);
position: absolute; font-size: 3rem;
width: 1px; height: 1px; font-weight: 700;
padding: 0; margin: -1px; margin-bottom: 1rem;
overflow: hidden; }
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0; .hero p {
} font-size: 1.2rem;
h1 { max-width: 600px;
font-size: 3rem; margin: 0 auto 2rem auto;
font-weight: 700; }
margin: 0 0 1rem;
letter-spacing: -1px; .hero .btn-cta {
} font-family: var(--heading-font);
p { font-size: 1.1rem;
margin: 0.5rem 0; color: var(--primary-color);
font-size: 1.1rem; background-color: var(--surface-color);
} padding: 0.8rem 2.5rem;
code { border-radius: 50px;
background: rgba(0,0,0,0.2); text-decoration: none;
padding: 2px 6px; font-weight: 600;
border-radius: 4px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
}
footer { .features {
position: absolute; padding: 4rem 0;
bottom: 1rem; text-align: center;
font-size: 0.8rem; }
opacity: 0.7;
} .features h2 {
</style> 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> </head>
<body> <body>
<main>
<div class="card"> <header>
<h1>Analyzing your requirements and generating your website…</h1> <div class="container">
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes"> <nav class="nav-bar">
<span class="sr-only">Loading…</span> <a href="/" class="logo">EduTask</a>
</div> <div class="nav-buttons">
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p> <button class="btn btn-login">Login</button>
<p class="hint">This page will update automatically as the plan is implemented.</p> <button class="btn btn-register">Register</button>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p> </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>&copy; 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">&times;</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> </div>
</main>
<footer> <!-- Register Modal -->
Page updated: <?= htmlspecialchars($now) ?> (UTC) <div id="register-modal" class="modal">
</footer> <div class="modal-content">
<div class="modal-header">
<h2>Daftar Akun Baru</h2>
<span class="close-btn">&times;</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> </body>
</html> </html>

23
login-google.php Normal file
View 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();
?>