-
-
- Are you sure you want to continue?
-
-
+
+
+
+
+
+
@@ -435,15 +489,26 @@
fetch('get_data.php')
.then(res => res.json())
.then(data => {
+
+ if (!Array.isArray(data)) {
+ if (data && data.error === 'Not authenticated') {
+ new bootstrap.Modal(document.getElementById('loginModal')).show();
+ return;
+ }
+ console.error('get_data.php returned non-array:', data);
+ return;
+ }
+
const container = document.getElementById('projectGrid');
container.innerHTML = '';
data.forEach((project, idx) => {
- const projectId = `project-${project.id}`;
- const colorClass = `project-color-${idx % 10}`;
+ const projectId = `project-${project.id}`;
+ const colorClass = `project-color-${idx % 10}`;
- const col = document.createElement('div');
+ const col = document.createElement('div');
+
col.className = `col-12 col-sm-6 col-md-4 col-lg-2 col-xl-2 col-xxl-1 g-2 m-0 project-column`;
col.dataset.projectId = project.id;
col.classList.add("draggable-project");
@@ -660,22 +725,6 @@ document.querySelectorAll('.project-column').forEach(col => {
});
});
- let itemToDelete = null;
-
- document.querySelectorAll('.delete-task-btn').forEach(btn => {
- btn.addEventListener('click', () => {
- itemToDelete = btn.dataset.taskId;
- const modal = new bootstrap.Modal(document.getElementById('confirmDeleteModal'));
- modal.show();
- });
- });
-
- document.getElementById('confirmDeleteBtn').addEventListener('click', () => {
- if (itemToDelete) {
- fetch(`delete_task.php?id=${itemToDelete}`)
- .then(() => location.reload()); // or re-fetch your UI
- }
- });
});
}
@@ -744,23 +793,6 @@ document.querySelectorAll('.project-column').forEach(col => {
});
});
- document.getElementById('settingsForm').addEventListener('submit', function (e) {
- e.preventDefault();
-
- const newTitle = document.getElementById('titleText').value.trim();
- const newIcon = document.getElementById('iconClass').value.trim();
-
- if (newTitle) {
- document.getElementById('appTitle').childNodes[1].textContent = newTitle;
- }
-
- if (newIcon) {
- const icon = document.getElementById('appIcon');
- icon.className = `bi ${newIcon} me-2`;
- }
-
- bootstrap.Modal.getInstance(document.getElementById('settingsModal')).hide();
- });
document.getElementById('settingsForm').addEventListener('submit', function (e) {
e.preventDefault();
@@ -782,6 +814,7 @@ document.querySelectorAll('.project-column').forEach(col => {
});
});
+
document.getElementById('resetSettings').addEventListener('click', () => {
fetch('reset_settings.php')
.then(() => {
@@ -789,11 +822,102 @@ document.querySelectorAll('.project-column').forEach(col => {
});
});
- window.addEventListener('DOMContentLoaded', () => {
- loadSettings();
- });
+
+ document.getElementById('showRegisterBtn').addEventListener('click', () => {
+ bootstrap.Modal.getInstance(document.getElementById('loginModal')).hide();
+ new bootstrap.Modal(document.getElementById('registerModal')).show();
+ });
+
+ document.getElementById('backToLoginBtn').addEventListener('click', () => {
+ bootstrap.Modal.getInstance(document.getElementById('registerModal')).hide();
+ new bootstrap.Modal(document.getElementById('loginModal')).show();
+ });
+
+ document.getElementById('loginForm').addEventListener('submit', (e) => {
+ e.preventDefault();
+ const email = document.getElementById('loginEmail').value.trim();
+ const password = document.getElementById('loginPassword').value;
+
+ const err = document.getElementById('loginError');
+ err.style.display = 'none';
+ err.textContent = '';
+
+ fetch('login.php', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password })
+ })
+ .then(async r => {
+ const data = await r.json();
+ if (!r.ok) throw new Error(data.error || 'Login failed');
+ return data;
+ })
+ .then(data => {
+ bootstrap.Modal.getInstance(document.getElementById('loginModal')).hide();
+
+ if (!data.user.can_manage_settings) {
+ document.querySelectorAll('.settings-btn').forEach(b => b.style.display = 'none');
+ }
+
+ fetchProjects();
+ })
+ .catch(ex => {
+ err.textContent = ex.message;
+ err.style.display = 'block';
+ });
+ });
+
+
+ document.getElementById('registerForm').addEventListener('submit', (e) => {
+ e.preventDefault();
+ const email = document.getElementById('registerEmail').value.trim();
+ const password = document.getElementById('registerPassword').value;
+
+ const err = document.getElementById('registerError');
+ const ok = document.getElementById('registerSuccess');
+ err.style.display = 'none'; err.textContent = '';
+ ok.style.display = 'none'; ok.textContent = '';
+
+ fetch('register.php', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password })
+ })
+ .then(async r => {
+ const data = await r.json();
+ if (!r.ok) throw new Error(data.error || 'Registration failed');
+ return data;
+ })
+ .then(() => {
+ ok.textContent = 'Account created. You can sign in now.';
+ ok.style.display = 'block';
+ })
+ .catch(ex => {
+ err.textContent = ex.message;
+ err.style.display = 'block';
+ });
+ });
- document.addEventListener('DOMContentLoaded', fetchProjects);
+ document.addEventListener('DOMContentLoaded', () => {
+ loadSettings();
+
+ fetch('me.php')
+ .then(r => r.json())
+ .then(me => {
+ if (!me.logged_in) {
+ const modal = new bootstrap.Modal(document.getElementById('loginModal'));
+ modal.show();
+ return;
+ }
+
+ // Hide settings button if user can't manage settings
+ if (!me.user.can_manage_settings) {
+ document.querySelectorAll('.settings-btn').forEach(b => b.style.display = 'none');
+ }
+
+ fetchProjects();
+ });
+ });
diff --git a/login.php b/login.php
new file mode 100644
index 0000000..1f24908
--- /dev/null
+++ b/login.php
@@ -0,0 +1,44 @@
+prepare("
+ SELECT u.id, u.email, u.password_hash,
+ r.name AS role_name,
+ r.can_manage_settings
+ FROM users u
+ JOIN roles r ON r.id = u.role_id
+ WHERE u.email = ?
+ LIMIT 1
+");
+$stmt->execute([$email]);
+$user = $stmt->fetch(PDO::FETCH_ASSOC);
+
+if (!$user || !password_verify($password, $user['password_hash'])) {
+ http_response_code(401);
+ echo json_encode(['success' => false, 'error' => 'Invalid credentials']);
+ exit;
+}
+
+$_SESSION['user'] = [
+ 'id' => intval($user['id']),
+ 'email' => $user['email'],
+ 'role' => $user['role_name'],
+ 'can_manage_settings' => intval($user['can_manage_settings']),
+];
+
+echo json_encode([
+ 'success' => true,
+ 'user' => [
+ 'id' => $_SESSION['user']['id'],
+ 'email' => $_SESSION['user']['email'],
+ 'role' => $_SESSION['user']['role'],
+ 'can_manage_settings' => $_SESSION['user']['can_manage_settings'],
+ ]
+]);
diff --git a/logout.php b/logout.php
new file mode 100644
index 0000000..87b2e25
--- /dev/null
+++ b/logout.php
@@ -0,0 +1,8 @@
+ true]);
diff --git a/me.php b/me.php
new file mode 100644
index 0000000..057b324
--- /dev/null
+++ b/me.php
@@ -0,0 +1,18 @@
+ false]);
+ exit;
+}
+
+echo json_encode([
+ 'logged_in' => true,
+ 'user' => [
+ 'id' => intval($_SESSION['user']['id']),
+ 'email' => $_SESSION['user']['email'],
+ 'role' => $_SESSION['user']['role'],
+ 'can_manage_settings' => intval($_SESSION['user']['can_manage_settings']),
+ ]
+]);
diff --git a/register.php b/register.php
new file mode 100644
index 0000000..49d6020
--- /dev/null
+++ b/register.php
@@ -0,0 +1,42 @@
+ false, 'error' => 'Invalid email']);
+ exit;
+}
+if (strlen($password) < 8) {
+ http_response_code(400);
+ echo json_encode(['success' => false, 'error' => 'Password must be at least 8 characters']);
+ exit;
+}
+
+$hash = password_hash($password, PASSWORD_DEFAULT);
+
+// Standard role id from roles table
+$stmt = $pdo->prepare("SELECT id FROM roles WHERE name = 'standard' LIMIT 1");
+$stmt->execute();
+$role_id = intval($stmt->fetchColumn());
+
+if ($role_id <= 0) {
+ http_response_code(500);
+ echo json_encode(['success' => false, 'error' => "Role 'standard' not found"]);
+ exit;
+}
+
+try {
+ $stmt = $pdo->prepare("INSERT INTO users (email, password_hash, role_id) VALUES (?, ?, ?)");
+ $stmt->execute([$email, $hash, $role_id]);
+ echo json_encode(['success' => true]);
+} catch (Throwable $e) {
+ http_response_code(409);
+ echo json_encode(['success' => false, 'error' => 'Account already exists']);
+}
diff --git a/reset_settings.php b/reset_settings.php
index f7ac3b2..815b099 100644
--- a/reset_settings.php
+++ b/reset_settings.php
@@ -1,6 +1,11 @@
prepare("UPDATE settings SET title = 'tinyTask', icon_class = 'kanban', icon_color = '#ff0000' WHERE id = 1");
$stmt->execute();
+
echo json_encode(['success' => true]);
-?>
diff --git a/update_project_order.php b/update_project_order.php
index ff32701..23c9f96 100644
--- a/update_project_order.php
+++ b/update_project_order.php
@@ -1,25 +1,27 @@
false, 'message' => 'Invalid input']);
exit;
}
-$stmt = $pdo->prepare("UPDATE projects SET sort_order = ? WHERE id = ?");
+$stmt = $pdo->prepare("UPDATE projects SET sort_order = ? WHERE id = ? AND user_id = ?");
foreach ($data as $item) {
- $id = $item['id'];
- $order = $item['order'];
+ $id = $item['id'] ?? null;
+ $order = $item['order'] ?? null;
if (!is_numeric($id) || !is_numeric($order)) continue;
- $stmt->execute([$order, $id]);
+ $stmt->execute([(int)$order, (int)$id, $user_id]);
}
echo json_encode(['success' => true]);
-exit;
diff --git a/update_settings.php b/update_settings.php
index 3b0c1b1..d524977 100644
--- a/update_settings.php
+++ b/update_settings.php
@@ -1,13 +1,17 @@
prepare("UPDATE settings SET title = ?, icon_class = ?, icon_color = ? WHERE id = 1");
$stmt->execute([$title, $icon, $color]);
echo json_encode(['success' => true]);
-?>
diff --git a/update_task_order.php b/update_task_order.php
index fe599fc..14e1493 100644
--- a/update_task_order.php
+++ b/update_task_order.php
@@ -1,11 +1,25 @@
prepare("UPDATE tasks SET task_order = ? WHERE id = ?");
- foreach ($data['task_ids'] as $order => $id) {
- $stmt->execute([$order, $id]);
- }
- echo json_encode
+require_login();
+$user_id = current_user_id();
+
+$data = json_decode(file_get_contents('php://input'), true);
+if (!$data || !is_array($data)) {
+ echo json_encode(['success' => false, 'message' => 'Invalid input']);
+ exit;
+}
+
+$stmt = $pdo->prepare("UPDATE tasks SET task_order = ? WHERE id = ? AND user_id = ?");
+
+foreach ($data as $item) {
+ $id = $item['id'] ?? null;
+ $order = $item['order'] ?? null;
+ if (!is_numeric($id) || !is_numeric($order)) continue;
+ $stmt->execute([(int)$order, (int)$id, $user_id]);
+}
+
+echo json_encode(['success' => true]);