Compare commits
12 Commits
c247631de6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9fd7fbd2e5 | |||
|
|
26bacd8928 | ||
| fe610b094d | |||
|
|
efc1fc3906 | ||
| 457170a1ae | |||
| 03fb096317 | |||
| 8fc8d737e5 | |||
| f879d25bc7 | |||
| 71a6b44890 | |||
| fd93da618c | |||
| a0d00ac21c | |||
| 11889e3f93 |
41
add_profile.php
Normal file
41
add_profile.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$user_id = current_user_id();
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$name = trim($data['name'] ?? '');
|
||||||
|
|
||||||
|
if ($name === '') {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Profile name is required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent duplicate profile names per user
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM profiles WHERE user_id = ? AND name = ?");
|
||||||
|
$stmt->execute([$user_id, $name]);
|
||||||
|
|
||||||
|
if ($stmt->fetchColumn()) {
|
||||||
|
http_response_code(409);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Profile already exists']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new profile
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO profiles (user_id, name, is_default) VALUES (?, ?, 0)");
|
||||||
|
$stmt->execute([$user_id, $name]);
|
||||||
|
|
||||||
|
$newProfileId = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Make it active immediately
|
||||||
|
$_SESSION['active_profile_id'] = (int)$newProfileId;
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'profile_id' => $newProfileId
|
||||||
|
]);
|
||||||
@@ -1,15 +1,29 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php';
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
header('Content-Type: application/json');
|
||||||
if (!isset($data['name']) || empty(trim($data['name']))) {
|
require_login();
|
||||||
|
|
||||||
|
$user_id = current_user_id();
|
||||||
|
$profile_id = $_SESSION['active_profile_id'] ?? null;
|
||||||
|
|
||||||
|
if (!$profile_id) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['error' => 'Project name is required']);
|
echo json_encode(['success' => false, 'error' => 'No active profile']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO projects (name) VALUES (?)");
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
$stmt->execute([trim($data['name'])]);
|
$name = trim($data['name'] ?? '');
|
||||||
|
|
||||||
|
if ($name === '') {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Project name is required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO projects (user_id, profile_id, name) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$user_id, $profile_id, $name]);
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
|
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
|
||||||
?>
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php';
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$user_id = current_user_id();
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
$name = trim($data['name'] ?? '');
|
$name = trim($data['name'] ?? '');
|
||||||
@@ -7,12 +13,20 @@ $task_id = intval($data['task_id'] ?? 0);
|
|||||||
|
|
||||||
if ($name === '' || $task_id <= 0) {
|
if ($name === '' || $task_id <= 0) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['error' => 'Invalid input']);
|
echo json_encode(['success' => false, 'error' => 'Invalid input']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO subtasks (task_id, name) VALUES (?, ?)");
|
// Ensure task belongs to this user
|
||||||
$stmt->execute([$task_id, $name]);
|
$stmt = $pdo->prepare("SELECT id FROM tasks WHERE id = ? AND user_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$task_id, $user_id]);
|
||||||
|
if (!$stmt->fetchColumn()) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Forbidden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO subtasks (user_id, task_id, name) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$user_id, $task_id, $name]);
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
|
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
|
||||||
?>
|
|
||||||
|
|||||||
22
add_task.php
22
add_task.php
@@ -1,5 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php';
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$user_id = current_user_id();
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
$name = trim($data['name'] ?? '');
|
$name = trim($data['name'] ?? '');
|
||||||
@@ -7,12 +13,20 @@ $project_id = intval($data['project_id'] ?? 0);
|
|||||||
|
|
||||||
if ($name === '' || $project_id <= 0) {
|
if ($name === '' || $project_id <= 0) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['error' => 'Invalid input']);
|
echo json_encode(['success' => false, 'error' => 'Invalid input']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO tasks (project_id, name) VALUES (?, ?)");
|
// Ensure project belongs to this user
|
||||||
$stmt->execute([$project_id, $name]);
|
$stmt = $pdo->prepare("SELECT id FROM projects WHERE id = ? AND user_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$project_id, $user_id]);
|
||||||
|
if (!$stmt->fetchColumn()) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Forbidden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO tasks (user_id, project_id, name) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$user_id, $project_id, $name]);
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
|
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
|
||||||
?>
|
|
||||||
|
|||||||
36
auth.php
Normal file
36
auth.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
// auth.php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_logged_in(): bool {
|
||||||
|
return isset($_SESSION['user']) && isset($_SESSION['user']['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function current_user_id(): int {
|
||||||
|
return intval($_SESSION['user']['id'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function current_user_can_manage_settings(): bool {
|
||||||
|
return !empty($_SESSION['user']['can_manage_settings']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function require_login(): void {
|
||||||
|
if (!is_logged_in()) {
|
||||||
|
http_response_code(401);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Not authenticated']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function require_can_manage_settings(): void {
|
||||||
|
require_login();
|
||||||
|
if (!current_user_can_manage_settings()) {
|
||||||
|
http_response_code(403);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Forbidden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php';
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
require_login();
|
||||||
|
$user_id = current_user_id();
|
||||||
|
|
||||||
$id = intval($_GET['id'] ?? 0);
|
$id = intval($_GET['id'] ?? 0);
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
$pdo->prepare("DELETE FROM projects WHERE id = ?")->execute([$id]);
|
$pdo->prepare("DELETE FROM projects WHERE id = ? AND user_id = ?")->execute([$id, $user_id]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php';
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
require_login();
|
||||||
|
$user_id = current_user_id();
|
||||||
|
|
||||||
$id = intval($_GET['id'] ?? 0);
|
$id = intval($_GET['id'] ?? 0);
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
$pdo->prepare("DELETE FROM subtasks WHERE id = ?")->execute([$id]);
|
$pdo->prepare("DELETE FROM subtasks WHERE id = ? AND user_id = ?")->execute([$id, $user_id]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php';
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
require_login();
|
||||||
|
$user_id = current_user_id();
|
||||||
|
|
||||||
$id = intval($_GET['id'] ?? 0);
|
$id = intval($_GET['id'] ?? 0);
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
$pdo->prepare("DELETE FROM tasks WHERE id = ?")->execute([$id]);
|
$pdo->prepare("DELETE FROM tasks WHERE id = ? AND user_id = ?")->execute([$id, $user_id]);
|
||||||
}
|
}
|
||||||
|
|||||||
26
get_data.php
26
get_data.php
@@ -1,19 +1,33 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php';
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
$projects = $pdo->query("SELECT * FROM projects ORDER BY sort_order ASC")->fetchAll();
|
header('Content-Type: application/json');
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$user_id = current_user_id();
|
||||||
|
$profile_id = $_SESSION['active_profile_id'] ?? null;
|
||||||
|
|
||||||
|
if (!$profile_id) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No active profile']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM projects WHERE user_id = ? AND profile_id = ? ORDER BY sort_order ASC");
|
||||||
|
$stmt->execute([$user_id, $profile_id]);
|
||||||
|
$projects = $stmt->fetchAll();
|
||||||
|
|
||||||
foreach ($projects as &$project) {
|
foreach ($projects as &$project) {
|
||||||
$stmt = $pdo->prepare("SELECT * FROM tasks WHERE project_id = ? ORDER BY created_at");
|
$stmt = $pdo->prepare("SELECT * FROM tasks WHERE project_id = ? AND user_id = ? ORDER BY created_at");
|
||||||
$stmt->execute([$project['id']]);
|
$stmt->execute([$project['id'], $user_id]);
|
||||||
$project['tasks'] = $stmt->fetchAll();
|
$project['tasks'] = $stmt->fetchAll();
|
||||||
|
|
||||||
foreach ($project['tasks'] as &$task) {
|
foreach ($project['tasks'] as &$task) {
|
||||||
$stmt = $pdo->prepare("SELECT * FROM subtasks WHERE task_id = ? ORDER BY created_at");
|
$stmt = $pdo->prepare("SELECT * FROM subtasks WHERE task_id = ? AND user_id = ? ORDER BY created_at");
|
||||||
$stmt->execute([$task['id']]);
|
$stmt->execute([$task['id'], $user_id]);
|
||||||
$task['subtasks'] = $stmt->fetchAll();
|
$task['subtasks'] = $stmt->fetchAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode($projects);
|
echo json_encode($projects);
|
||||||
?>
|
|
||||||
|
|||||||
18
get_profiles.php
Normal file
18
get_profiles.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$user_id = current_user_id();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT id, name, is_default FROM profiles WHERE user_id = ? ORDER BY is_default DESC, name ASC");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$profiles = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'active_profile_id' => $_SESSION['active_profile_id'] ?? null,
|
||||||
|
'profiles' => $profiles
|
||||||
|
]);
|
||||||
364
index.php
364
index.php
@@ -78,11 +78,6 @@
|
|||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-project-btn {
|
|
||||||
font-size: 1.5rem; /* increase to your desired size */
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-btn {
|
.settings-btn {
|
||||||
font-size: 1.5rem; /* increase to your desired size */
|
font-size: 1.5rem; /* increase to your desired size */
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -136,6 +131,52 @@
|
|||||||
color: #d00;
|
color: #d00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-menu-btn {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
padding: 0.5rem 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-action-btn {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
padding: 0.6rem 0.9rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-select {
|
||||||
|
height: 32px; /* match your header-action-btn buttons */
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700; /* or 700 for stronger bold */
|
||||||
|
padding-right: 2.5rem; /* key: reserve space for the arrow */
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-project-btn {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600; /* or 700 for stronger bold */
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown {
|
||||||
|
min-width: 120px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown .dropdown-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown .dropdown-item,
|
||||||
|
.custom-dropdown .dropdown-item-text {
|
||||||
|
font-size: 1.5rem !important;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.project-color-0 { background-color: #f9f9fc; }
|
.project-color-0 { background-color: #f9f9fc; }
|
||||||
.project-color-1 { background-color: #eefaf5; }
|
.project-color-1 { background-color: #eefaf5; }
|
||||||
.project-color-2 { background-color: #fef7e0; }
|
.project-color-2 { background-color: #fef7e0; }
|
||||||
@@ -228,9 +269,37 @@
|
|||||||
<i class="bi bi-gear-fill"></i>
|
<i class="bi bi-gear-fill"></i>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<button class="btn btn-sm btn-success add-project-btn" data-bs-toggle="modal" data-bs-target="#addProjectModal" title="Add Project">
|
|
||||||
<i class="bi bi-plus"></i>
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<select id="profileSelect" class="form-select btn-lg profile-select" style="width: 140px;"></select>
|
||||||
|
<!-- Add Project -->
|
||||||
|
<button class="btn btn-success btn-lg header-action-btn"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addProjectModal">
|
||||||
|
<i class="bi bi-plus-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- User Dropdown -->
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-outline-secondary btn-lg header-action-btn"
|
||||||
|
type="button"
|
||||||
|
id="menuButton"
|
||||||
|
data-bs-toggle="dropdown">
|
||||||
|
<i class="bi bi-person-circle"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end custom-dropdown" aria-labelledby="menuButton">
|
||||||
|
<li class="dropdown-item-text fw-semibold fs-5" id="dropdownUserEmail"></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item fs-5" type="button" id="logoutBtn">
|
||||||
|
<i class="bi bi-box-arrow-right"></i>
|
||||||
|
<span>Logout</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
|
||||||
@@ -362,6 +431,64 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal: Login -->
|
||||||
|
<div class="modal fade" id="loginModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<form id="loginForm" class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Sign In</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Email or Username</label>
|
||||||
|
<input type="text" class="form-control" id="loginEmail" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Password</label>
|
||||||
|
<input type="password" class="form-control" id="loginPassword" required>
|
||||||
|
</div>
|
||||||
|
<div class="text-danger" id="loginError" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer d-flex justify-content-between w-100">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" id="showRegisterBtn">Create account</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Sign In</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal: Register -->
|
||||||
|
<div class="modal fade" id="registerModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<form id="registerForm" class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Create Account</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Email</label>
|
||||||
|
<input type="email" class="form-control" id="registerEmail" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Username (optional)</label>
|
||||||
|
<input type="text" class="form-control" id="registerUsername">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Password (8+ chars)</label>
|
||||||
|
<input type="password" class="form-control" id="registerPassword" required minlength="8">
|
||||||
|
</div>
|
||||||
|
<div class="text-danger" id="registerError" style="display:none;"></div>
|
||||||
|
<div class="text-success" id="registerSuccess" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer d-flex justify-content-between w-100">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" id="backToLoginBtn">Back</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Create</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
|
<script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
|
||||||
@@ -427,14 +554,42 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadProfiles() {
|
||||||
|
fetch('get_profiles.php')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) return;
|
||||||
|
|
||||||
|
const sel = document.getElementById('profileSelect');
|
||||||
|
sel.innerHTML = '';
|
||||||
|
|
||||||
|
data.profiles.forEach(p => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = p.id;
|
||||||
|
opt.textContent = p.name;
|
||||||
|
sel.appendChild(opt);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.active_profile_id) {
|
||||||
|
sel.value = String(data.active_profile_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function fetchProjects() {
|
function fetchProjects() {
|
||||||
fetch('get_data.php')
|
fetch('get_data.php')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.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');
|
const container = document.getElementById('projectGrid');
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
@@ -444,6 +599,7 @@
|
|||||||
const colorClass = `project-color-${idx % 10}`;
|
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.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.dataset.projectId = project.id;
|
||||||
col.classList.add("draggable-project");
|
col.classList.add("draggable-project");
|
||||||
@@ -660,22 +816,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 +884,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) {
|
document.getElementById('settingsForm').addEventListener('submit', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -782,6 +905,7 @@ document.querySelectorAll('.project-column').forEach(col => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
document.getElementById('resetSettings').addEventListener('click', () => {
|
document.getElementById('resetSettings').addEventListener('click', () => {
|
||||||
fetch('reset_settings.php')
|
fetch('reset_settings.php')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -789,11 +913,165 @@ 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.addEventListener('DOMContentLoaded', fetchProjects);
|
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();
|
||||||
|
|
||||||
|
// Show/hide settings button according to permissions
|
||||||
|
if (!data.user.can_manage_settings) {
|
||||||
|
document.querySelectorAll('.settings-btn').forEach(b => b.style.display = 'none');
|
||||||
|
} else {
|
||||||
|
document.querySelectorAll('.settings-btn').forEach(b => b.style.display = '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user email or username if exists in the dropdown and refresh profiles + projects
|
||||||
|
if (data.user) {
|
||||||
|
const userEl = document.getElementById('dropdownUserEmail');
|
||||||
|
if (userEl) userEl.textContent = (data.user.username && data.user.username.trim())
|
||||||
|
? data.user.username
|
||||||
|
: data.user.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProfiles();
|
||||||
|
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 username = document.getElementById('registerUsername').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, username: username || null, 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.getElementById('profileSelect').addEventListener('change', () => {
|
||||||
|
const profileId = parseInt(document.getElementById('profileSelect').value, 10);
|
||||||
|
|
||||||
|
fetch('set_profile.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ profile_id: profileId })
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) return;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user name to user menu
|
||||||
|
if (me.logged_in) {
|
||||||
|
document.getElementById('dropdownUserEmail').textContent =
|
||||||
|
(me.user.username && me.user.username.trim()) ? me.user.username : me.user.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProfiles();
|
||||||
|
fetchProjects();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const logoutBtn = document.getElementById('logoutBtn');
|
||||||
|
|
||||||
|
if (logoutBtn) {
|
||||||
|
logoutBtn.addEventListener('click', () => {
|
||||||
|
fetch('logout.php')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) return;
|
||||||
|
|
||||||
|
// Clear UI
|
||||||
|
document.getElementById('projectGrid').innerHTML = '';
|
||||||
|
|
||||||
|
// Clear Profile List
|
||||||
|
document.getElementById('profileSelect').innerHTML = '';
|
||||||
|
|
||||||
|
// Clear User Email
|
||||||
|
document.getElementById('dropdownUserEmail').textContent = '';
|
||||||
|
|
||||||
|
// Show login modal
|
||||||
|
new bootstrap.Modal(document.getElementById('loginModal')).show();
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Logout failed:', err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
79
login.php
Normal file
79
login.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
// Keep front-end field name "email" for compatibility; it can now be email OR username
|
||||||
|
$identifierRaw = trim($data['email'] ?? '');
|
||||||
|
$identifier = strtolower($identifierRaw);
|
||||||
|
$password = strval($data['password'] ?? '');
|
||||||
|
|
||||||
|
if ($identifier === '') {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Email or username is required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isEmail = filter_var($identifier, FILTER_VALIDATE_EMAIL) !== false;
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT u.id, u.email, u.username, 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 " . ($isEmail ? "u.email = ?" : "u.username = ?") . "
|
||||||
|
LIMIT 1
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$identifier]);
|
||||||
|
$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' => (int)$user['id'],
|
||||||
|
'email' => $user['email'],
|
||||||
|
'username' => $user['username'], // NEW
|
||||||
|
'role' => $user['role_name'],
|
||||||
|
'can_manage_settings' => (int)$user['can_manage_settings'],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set active profile for this session (default profile if available)
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM profiles WHERE user_id = ? AND is_default = 1 LIMIT 1");
|
||||||
|
$stmt->execute([$_SESSION['user']['id']]);
|
||||||
|
$profileId = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
if (!$profileId) {
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM profiles WHERE user_id = ? ORDER BY id ASC LIMIT 1");
|
||||||
|
$stmt->execute([$_SESSION['user']['id']]);
|
||||||
|
$profileId = $stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$profileId) {
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO profiles (user_id, name, is_default) VALUES (?, 'Default', 1)");
|
||||||
|
$stmt->execute([$_SESSION['user']['id']]);
|
||||||
|
$profileId = $pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['active_profile_id'] = (int)$profileId;
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'user' => [
|
||||||
|
'id' => $_SESSION['user']['id'],
|
||||||
|
'email' => $_SESSION['user']['email'],
|
||||||
|
'username' => $_SESSION['user']['username'], // NEW
|
||||||
|
'role' => $_SESSION['user']['role'],
|
||||||
|
'can_manage_settings' => $_SESSION['user']['can_manage_settings'],
|
||||||
|
],
|
||||||
|
'active_profile_id' => $_SESSION['active_profile_id']
|
||||||
|
]);
|
||||||
8
logout.php
Normal file
8
logout.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
require 'auth.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$_SESSION = [];
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
18
me.php
Normal file
18
me.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
require 'auth.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!is_logged_in()) {
|
||||||
|
echo json_encode(['logged_in' => 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']),
|
||||||
|
]
|
||||||
|
]);
|
||||||
98
register.php
Normal file
98
register.php
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$email = strtolower(trim($data['email'] ?? ''));
|
||||||
|
$usernameRaw = trim($data['username'] ?? '');
|
||||||
|
$username = $usernameRaw === '' ? null : strtolower($usernameRaw);
|
||||||
|
$password = strval($data['password'] ?? '');
|
||||||
|
|
||||||
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional username validation
|
||||||
|
if ($username !== null) {
|
||||||
|
if (strlen($username) < 3 || strlen($username) > 50) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Username must be between 3 and 50 characters']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// letters/numbers + . _ - ; must start/end with letter/number
|
||||||
|
if (!preg_match('/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/', $username)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'Username may contain letters, numbers, underscore, hyphen, dot, and must start/end with a letter or number'
|
||||||
|
]);
|
||||||
|
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 = (int)$stmt->fetchColumn();
|
||||||
|
|
||||||
|
if ($role_id <= 0) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'error' => "Role 'standard' not found"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Friendly conflict checks (DB unique constraints should still exist)
|
||||||
|
$stmt = $pdo->prepare('SELECT 1 FROM users WHERE email = ? LIMIT 1');
|
||||||
|
$stmt->execute([$email]);
|
||||||
|
if ($stmt->fetchColumn()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
http_response_code(409);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Email already in use']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($username !== null) {
|
||||||
|
$stmt = $pdo->prepare('SELECT 1 FROM users WHERE username = ? LIMIT 1');
|
||||||
|
$stmt->execute([$username]);
|
||||||
|
if ($stmt->fetchColumn()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
http_response_code(409);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Username already in use']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user (username is nullable)
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO users (email, username, password_hash, role_id) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$email, $username, $hash, $role_id]);
|
||||||
|
$userId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Create default profile for this user
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO profiles (user_id, name, is_default) VALUES (?, 'Default', 1)");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
|
||||||
|
http_response_code(409);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Account already exists']);
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php';
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_can_manage_settings();
|
||||||
|
|
||||||
$stmt = $pdo->prepare("UPDATE settings SET title = 'tinyTask', icon_class = 'kanban', icon_color = '#ff0000' WHERE id = 1");
|
$stmt = $pdo->prepare("UPDATE settings SET title = 'tinyTask', icon_class = 'kanban', icon_color = '#ff0000' WHERE id = 1");
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
echo json_encode(['success' => true]);
|
echo json_encode(['success' => true]);
|
||||||
?>
|
|
||||||
|
|||||||
30
set_profile.php
Normal file
30
set_profile.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$user_id = current_user_id();
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$profile_id = (int)($input['profile_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($profile_id <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid profile_id']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM profiles WHERE id = ? AND user_id = ?");
|
||||||
|
$stmt->execute([$profile_id, $user_id]);
|
||||||
|
|
||||||
|
if (!$stmt->fetchColumn()) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Profile not allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['active_profile_id'] = $profile_id;
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'active_profile_id' => $profile_id]);
|
||||||
@@ -1,25 +1,27 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php';
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
require_login();
|
||||||
|
$user_id = current_user_id();
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
if (!$data || !is_array($data)) {
|
if (!$data || !is_array($data)) {
|
||||||
echo json_encode(['success' => false, 'message' => 'Invalid input']);
|
echo json_encode(['success' => false, 'message' => 'Invalid input']);
|
||||||
exit;
|
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) {
|
foreach ($data as $item) {
|
||||||
$id = $item['id'];
|
$id = $item['id'] ?? null;
|
||||||
$order = $item['order'];
|
$order = $item['order'] ?? null;
|
||||||
|
|
||||||
if (!is_numeric($id) || !is_numeric($order)) continue;
|
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]);
|
echo json_encode(['success' => true]);
|
||||||
exit;
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php';
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_can_manage_settings();
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
$title = $data['title'] ?? 'tinyTask';
|
$title = $data['title'] ?? 'tinyTask';
|
||||||
@@ -10,4 +15,3 @@ $stmt = $pdo->prepare("UPDATE settings SET title = ?, icon_class = ?, icon_color
|
|||||||
$stmt->execute([$title, $icon, $color]);
|
$stmt->execute([$title, $icon, $color]);
|
||||||
|
|
||||||
echo json_encode(['success' => true]);
|
echo json_encode(['success' => true]);
|
||||||
?>
|
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
<?php
|
<?php
|
||||||
require 'db.php'; // Adjust path if needed
|
require 'db.php';
|
||||||
|
require 'auth.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"), true);
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
if (isset($data['task_ids']) && is_array($data['task_ids'])) {
|
require_login();
|
||||||
$stmt = $pdo->prepare("UPDATE tasks SET task_order = ? WHERE id = ?");
|
$user_id = current_user_id();
|
||||||
foreach ($data['task_ids'] as $order => $id) {
|
|
||||||
$stmt->execute([$order, $id]);
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (!$data || !is_array($data)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid input']);
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
echo json_encode
|
|
||||||
|
$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]);
|
||||||
|
|||||||
Reference in New Issue
Block a user