FlexPass_v.0005

This commit is contained in:
Flatlogic Bot 2025-09-26 19:57:10 +00:00
parent 3d274404c6
commit 0f1653e913
12 changed files with 461 additions and 50 deletions

View File

@ -36,8 +36,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$errors[] = "Client ID already exists.";
} else {
// Insert into database
$stmt = $pdo->prepare("INSERT INTO clients (client_id, name, status) VALUES (?, ?, ?)");
if ($stmt->execute([$clientId, $clientName, $status])) {
$stmt = $pdo->prepare("INSERT INTO clients (client_id, name, status, user_id) VALUES (?, ?, ?, ?)");
if ($stmt->execute([$clientId, $clientName, $status, $_SESSION['user_id']])) {
log_audit_event('client_create', $_SESSION['user_id'], 'client', $clientId);
header("Location: dashboard.php?status=client_added");
exit;

View File

@ -131,7 +131,13 @@ document.addEventListener('DOMContentLoaded', function () {
if (confirm('Are you sure you want to delete this note?')) {
const noteId = deleteButton.dataset.noteId;
fetch(`delete-note.php?note_id=${noteId}`)
const formData = new FormData();
formData.append('note_id', noteId);
fetch('delete-note.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -15,8 +15,8 @@ $viewingClient = null;
$clients = [];
if (isset($_GET['client_id'])) {
$stmt = $pdo->prepare("SELECT * FROM clients WHERE client_id = ?");
$stmt->execute([$_GET['client_id']]);
$stmt = $pdo->prepare("SELECT * FROM clients WHERE client_id = ? AND user_id = ?");
$stmt->execute([$_GET['client_id'], $_SESSION['user_id']]);
$viewingClient = $stmt->fetch(PDO::FETCH_ASSOC);
$credentials = [];
@ -35,7 +35,8 @@ if (isset($_GET['client_id'])) {
$notes = $noteStmt->fetchAll(PDO::FETCH_ASSOC);
}
} else {
$stmt = $pdo->query("SELECT * FROM clients ORDER BY name ASC");
$stmt = $pdo->prepare("SELECT * FROM clients WHERE user_id = ? ORDER BY name ASC");
$stmt->execute([$_SESSION['user_id']]);
$clients = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
@ -55,7 +56,7 @@ if (isset($_GET['client_id'])) {
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="dashboard.php">
<i class="bi bi-shield-lock"></i> FlexPass
<i class="bi bi-shield-lock"></i> ClientManager
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
@ -63,7 +64,7 @@ if (isset($_GET['client_id'])) {
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="dashboard.php">Clients</a>
<a class="nav-link active" aria-current="page" href="dashboard.php">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="audit-log.php">Audit Log</a>
@ -254,7 +255,9 @@ if (isset($_GET['client_id'])) {
<td><?php echo htmlspecialchars($client['name']); ?></td>
<td><span class="badge bg-<?php echo $client['status'] === 'active' ? 'success' : 'danger'; ?>"><?php echo htmlspecialchars($client['status']); ?></span></td>
<td>
<a href="?client_id=<?php echo htmlspecialchars($client['client_id']); ?>" class="btn btn-sm btn-outline-primary">View</a>
<a href="dashboard.php?client_id=<?php echo htmlspecialchars($client['client_id']); ?>" class="btn btn-sm btn-outline-primary">View</a>
<a href="edit-client.php?client_id=<?php echo htmlspecialchars($client['client_id']); ?>" class="btn btn-sm btn-outline-secondary">Edit</a>
<a href="delete-client.php?client_id=<?php echo htmlspecialchars($client['client_id']); ?>" class="btn btn-sm btn-outline-danger">Delete</a>
</td>
</tr>
<?php endforeach; ?>

View File

@ -4,25 +4,55 @@ require_once __DIR__ . '/config.php';
function run_migrations() {
$pdo = db();
$migrationsDir = __DIR__ . '/migrations';
$files = glob($migrationsDir . '/*.sql');
sort($files);
foreach ($files as $file) {
echo "Running migration: " . basename($file) . "...\n";
$sql = file_get_contents($file);
if ($sql === false) {
echo "Error: Could not read file " . basename($file) . ".\n";
// 1. Create migrations table if it doesn't exist
try {
$pdo->exec("CREATE TABLE IF NOT EXISTS `migrations` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`migration` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;");
} catch (PDOException $e) {
echo "Error creating migrations table: " . $e->getMessage() . "\n";
return false;
}
// 2. Get all migration files and executed migrations
$migrationFiles = glob($migrationsDir . '/*.sql');
sort($migrationFiles);
$stmt = $pdo->query("SELECT migration FROM migrations");
$executedMigrations = $stmt->fetchAll(PDO::FETCH_COLUMN);
// 3. Run migrations that have not been executed yet
foreach ($migrationFiles as $file) {
$migrationName = basename($file);
if (in_array($migrationName, $executedMigrations)) {
echo "Skipping already executed migration: {$migrationName}\n";
continue;
}
echo "Running migration: {$migrationName}...\n";
$sql = file_get_contents($file);
if ($sql === false) {
echo "Error: Could not read file {$migrationName}.\n";
continue;
}
try {
// DDL statements often have implicit commits, so transactions are not reliable.
$pdo->exec($sql);
$insertStmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?)");
$insertStmt->execute([$migrationName]);
echo "Success.\n";
} catch (PDOException $e) {
echo "Error executing migration " . basename($file) . ": " . $e->getMessage() . "\n";
echo "Error executing migration {$migrationName}: " . $e->getMessage() . "\n";
// Exit on first error
return false;
}
}
echo "All new migrations have been executed.\n";
return true;
}

View File

@ -0,0 +1 @@
ALTER TABLE `clients` ADD COLUMN `user_id` CHAR(36) NULL AFTER `status`;

View File

@ -0,0 +1,2 @@
ALTER TABLE `clients` MODIFY COLUMN `user_id` CHAR(36) NOT NULL;
ALTER TABLE `clients` ADD CONSTRAINT `fk_clients_user_id` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;

View File

@ -0,0 +1,37 @@
<?php
require_once __DIR__ . '/config.php';
$pdo = db();
// 1. Get the admin user's ID
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute(['admin@flexpass.local']);
$adminUser = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$adminUser) {
echo "Admin user not found. Please seed the database first (php db/seed.php).\n";
exit(1);
}
$adminId = $adminUser['id'];
echo "Found admin user ID: {$adminId}\n";
// 2. Update existing clients
try {
$updateStmt = $pdo->prepare("UPDATE clients SET user_id = ? WHERE user_id IS NULL OR user_id = ''");
$updateStmt->execute([$adminId]);
$rowCount = $updateStmt->rowCount();
echo "Updated {$rowCount} client(s) to belong to the admin user.\n";
} catch (PDOException $e) {
// This will fail if the column doesn't exist yet, which is fine.
echo "Could not update clients (the user_id column might not exist yet): " . $e->getMessage() . "\n";
}
// 3. Modify the migration to be safer
$migrationFile = __DIR__ . '/migrations/002_add_user_id_to_clients.sql';
$migrationSQL = "ALTER TABLE `clients` ADD COLUMN `user_id` CHAR(36);"; // Add as nullable first
file_put_contents($migrationFile, $migrationSQL);
echo "Migration 002 updated to be safer.\n";
?>

127
delete-client.php Normal file
View File

@ -0,0 +1,127 @@
<?php
session_start();
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/audit.php';
// If user is not logged in, redirect to login page
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$displayName = $_SESSION['user_display_name'] ?? 'User';
$clientId = $_GET['client_id'] ?? null;
$client = null;
$error = '';
if (!$clientId) {
header('Location: dashboard.php');
exit;
}
$pdo = db();
// Fetch client data to display its name in the confirmation message
try {
$stmt = $pdo->prepare("SELECT name FROM clients WHERE client_id = ? AND user_id = ?");
$stmt->execute([$clientId, $_SESSION['user_id']]);
$client = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$client) {
// If client not found or doesn't belong to the user, redirect.
header('Location: dashboard.php');
exit;
}
} catch (PDOException $e) {
$error = "Error fetching client data: " . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Check for confirmation
if (isset($_POST['confirm_delete'])) {
try {
// The ON DELETE CASCADE constraint will handle associated credentials and notes.
$stmt = $pdo->prepare("DELETE FROM clients WHERE client_id = ? AND user_id = ?");
$stmt->execute([$clientId, $_SESSION['user_id']]);
log_audit_event('client_delete', $_SESSION['user_id'], "Client '{$client['name']}' (ID: {$clientId}) and all associated data deleted.");
// Using session to pass success message
$_SESSION['success_message'] = "Client '" . htmlspecialchars($client['name']) . "' and all associated data have been deleted.";
header('Location: dashboard.php?status=client_deleted');
exit;
} catch (PDOException $e) {
$error = "Error deleting client: " . $e->getMessage();
}
} else {
// If not confirmed, just redirect
header('Location: dashboard.php?client_id=' . $clientId);
exit;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Delete Client - ClientManager</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="dashboard.php">ClientManager</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="audit-log.php">Audit Log</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> <?php echo htmlspecialchars($displayName); ?>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<h2>Delete Client</h2>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<?php if ($client): ?>
<div class="alert alert-warning">
<h4>Are you sure?</h4>
<p>This action will permanently delete the client <strong><?php echo htmlspecialchars($client['name']); ?></strong> and all of their associated credentials and notes. This cannot be undone.</p>
</div>
<form action="delete-client.php?client_id=<?php echo htmlspecialchars($clientId); ?>" method="post">
<button type="submit" name="confirm_delete" class="btn btn-danger">Yes, Delete Everything</button>
<a href="dashboard.php?client_id=<?php echo htmlspecialchars($clientId); ?>" class="btn btn-secondary">Cancel</a>
</form>
<?php else: ?>
<div class="alert alert-info">Client not found or you do not have permission to delete it.</div>
<a href="dashboard.php" class="btn btn-primary">Back to Dashboard</a>
<?php endif; ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -9,34 +9,92 @@ if (!isset($_SESSION['user_id'])) {
exit;
}
// Check if credential_id is provided
if (!isset($_GET['credential_id'])) {
$displayName = $_SESSION['user_display_name'] ?? 'User';
$credentialId = $_GET['credential_id'] ?? null;
$credential = null;
$error = '';
if (!$credentialId) {
header('Location: dashboard.php');
exit;
}
$pdo = db();
$credential_id = $_GET['credential_id'];
// Fetch the client_id for redirecting back
$stmt = $pdo->prepare("SELECT client_id FROM credentials WHERE credential_id = ?");
$stmt->execute([$credential_id]);
$credential = $stmt->fetch(PDO::FETCH_ASSOC);
if ($credential) {
$client_id = $credential['client_id'];
log_audit_event('credential_delete', $_SESSION['user_id'], 'credential', $credential_id);
// Delete the credential
$deleteStmt = $pdo->prepare("DELETE FROM credentials WHERE credential_id = ?");
$deleteStmt->execute([$credential_id]);
// Redirect back to the client detail page with a success message
header("Location: dashboard.php?client_id=$client_id&status=credential_deleted");
exit;
} else {
// Credential not found, just redirect
header('Location: dashboard.php');
exit;
// Fetch credential data to display its name and ensure it belongs to the user.
try {
$stmt = $pdo->prepare(
"SELECT c.credential_id, c.name, c.client_id FROM credentials c " .
"JOIN clients cl ON c.client_id = cl.client_id " .
"WHERE c.credential_id = ? AND cl.user_id = ?"
);
$stmt->execute([$credentialId, $_SESSION['user_id']]);
$credential = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$credential) {
// If credential not found or doesn't belong to the user, redirect.
header('Location: dashboard.php');
exit;
}
} catch (PDOException $e) {
$error = "Error fetching credential data: " . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['confirm_delete'])) {
try {
$stmt = $pdo->prepare("DELETE FROM credentials WHERE credential_id = ?");
$stmt->execute([$credentialId]);
log_audit_event('credential_delete', $_SESSION['user_id'], "Credential '{$credential['name']}' (ID: {$credentialId}) deleted.");
header('Location: dashboard.php?client_id=' . $credential['client_id'] . '&status=credential_deleted');
exit;
} catch (PDOException $e) {
$error = "Error deleting credential: " . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Delete Credential - ClientManager</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="dashboard.php">ClientManager</a>
</div>
</nav>
<div class="container mt-4">
<h2>Delete Credential</h2>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<?php if ($credential): ?>
<div class="alert alert-warning">
<h4>Are you sure?</h4>
<p>This action will permanently delete the credential <strong><?php echo htmlspecialchars($credential['name']); ?></strong>.</p>
</div>
<form action="delete-credential.php?credential_id=<?php echo htmlspecialchars($credentialId); ?>" method="post">
<button type="submit" name="confirm_delete" class="btn btn-danger">Yes, Delete</button>
<a href="dashboard.php?client_id=<?php echo htmlspecialchars($credential['client_id']); ?>" class="btn btn-secondary">Cancel</a>
</form>
<?php else: ?>
<div class="alert alert-info">Credential not found or you do not have permission to delete it.</div>
<a href="dashboard.php" class="btn btn-primary">Back to Dashboard</a>
<?php endif; ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -5,25 +5,33 @@ require_once __DIR__ . '/includes/audit.php';
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'message' => 'Invalid request method.']);
exit;
}
// If user is not logged in, return error
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'message' => 'User not logged in.']);
exit;
}
$noteId = $_GET['note_id'] ?? null;
$noteId = $_POST['note_id'] ?? null;
if ($noteId) {
try {
$pdo = db();
// First, get the client_id for redirection
$stmt = $pdo->prepare("SELECT client_id FROM notes WHERE note_id = ?");
$stmt->execute([$noteId]);
// Verify that the note belongs to a client of the logged-in user
$stmt = $pdo->prepare(
"SELECT n.note_id FROM notes n " .
"JOIN clients c ON n.client_id = c.client_id " .
"WHERE n.note_id = ? AND c.user_id = ?"
);
$stmt->execute([$noteId, $_SESSION['user_id']]);
$note = $stmt->fetch(PDO::FETCH_ASSOC);
$clientId = $note['client_id'] ?? null;
if ($clientId) {
if ($note) {
log_audit_event('note_delete', $_SESSION['user_id'], 'note', $noteId);
// Now, delete the note
@ -33,17 +41,16 @@ if ($noteId) {
echo json_encode(['success' => true]);
exit;
} else {
// Note not found or no client_id associated
echo json_encode(['success' => false, 'message' => 'Note not found.']);
echo json_encode(['success' => false, 'message' => 'Note not found or you do not have permission to delete it.']);
exit;
}
} catch (PDOException $e) {
// Optional: Log error
error_log("Error deleting note: " . $e->getMessage());
echo json_encode(['success' => false, 'message' => 'Database error.']);
exit;
}
} else {
// Redirect if note_id is missing
echo json_encode(['success' => false, 'message' => 'Note ID is required.']);
exit;
}

140
edit-client.php Normal file
View File

@ -0,0 +1,140 @@
<?php
session_start();
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/audit.php';
// If user is not logged in, redirect to login page
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$displayName = $_SESSION['user_display_name'] ?? 'User';
$clientId = $_GET['client_id'] ?? null;
$client = null;
$errors = [];
if (!$clientId) {
header('Location: dashboard.php');
exit;
}
$pdo = db();
// Fetch client data
try {
$stmt = $pdo->prepare("SELECT * FROM clients WHERE client_id = ? AND user_id = ?");
$stmt->execute([$clientId, $_SESSION['user_id']]);
$client = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$client) {
// If client not found or doesn't belong to the user, redirect.
header('Location: dashboard.php');
exit;
}
} catch (PDOException $e) {
$errors[] = "Error fetching client data: " . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$status = trim($_POST['status'] ?? '');
if (empty($name)) {
$errors[] = 'Client name is required.';
}
if (!in_array($status, ['active', 'inactive'])) {
$errors[] = 'Invalid status value.';
}
if (empty($errors)) {
try {
$stmt = $pdo->prepare("UPDATE clients SET name = ?, status = ? WHERE client_id = ? AND user_id = ?");
$stmt->execute([$name, $status, $clientId, $_SESSION['user_id']]);
log_audit_event('client_edit', $_SESSION['user_id'], "Client '{$name}' (ID: {$clientId}) updated.");
header('Location: dashboard.php?client_id=' . $clientId . '&status=client_updated');
exit;
} catch (PDOException $e) {
$errors[] = "Error updating client: " . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Client - ClientManager</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="dashboard.php">ClientManager</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="audit-log.php">Audit Log</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> <?php echo htmlspecialchars($displayName); ?>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<h2>Edit Client</h2>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach ($errors as $error): ?>
<li><?php echo htmlspecialchars($error); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($client): ?>
<form action="edit-client.php?client_id=<?php echo htmlspecialchars($clientId); ?>" method="post">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($client['name']); ?>" required>
</div>
<div class="mb-3">
<label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status">
<option value="active" <?php echo ($client['status'] === 'active') ? 'selected' : ''; ?>>Active</option>
<option value="inactive" <?php echo ($client['status'] === 'inactive') ? 'selected' : ''; ?>>Inactive</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="dashboard.php?client_id=<?php echo htmlspecialchars($clientId); ?>" class="btn btn-secondary">Cancel</a>
</form>
<?php else: ?>
<p>Client not found or you do not have permission to view it.</p>
<a href="dashboard.php" class="btn btn-primary">Back to Dashboard</a>
<?php endif; ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>