From 40e3f73aafa234a9e3003b0f023f2ede7e7c710a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Mar 2026 03:05:18 +0000 Subject: [PATCH] Add contact form DB storage and hidden staff inbox - contact.php now inserts submissions into MySQL via PDO prepared statements; raw values stored (htmlspecialchars moved to output only) - www/includes/db.php: shared PDO helper with auto-migration that adds the is_read column to existing deployments without a full DB reset - docker/mysql/init.sql: added is_read TINYINT column to contacts table for fresh deploys - www/pages/admin-inbox.php: self-contained staff inbox at /staff-portal with session-based password login, per-message mark-as-read, and mark-all-read; unread count shown in browser tab title - index.php: routes /staff-portal before public header/footer so the admin page is fully standalone - docker-compose.yml: ADMIN_PASS env var wired to web container Set ADMIN_PASS in .env (gitignored) before deploying. If the DB volume already exists, the auto-migration in db.php will add the is_read column automatically on first request. https://claude.ai/code/session_015wpwmheufcxkBuXivrSHhd --- docker-compose.yml | 1 + docker/mysql/init.sql | 1 + www/includes/db.php | 35 ++++++ www/index.php | 6 + www/pages/admin-inbox.php | 239 ++++++++++++++++++++++++++++++++++++++ www/pages/contact.php | 32 +++-- 6 files changed, 304 insertions(+), 10 deletions(-) create mode 100644 www/includes/db.php create mode 100644 www/pages/admin-inbox.php diff --git a/docker-compose.yml b/docker-compose.yml index 2c57894..4659f4b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: - DB_NAME=${DB_NAME} - DB_USER=${DB_USER} - DB_PASS=${DB_PASS} + - ADMIN_PASS=${ADMIN_PASS} depends_on: - db networks: diff --git a/docker/mysql/init.sql b/docker/mysql/init.sql index 970868a..ad7eda5 100644 --- a/docker/mysql/init.sql +++ b/docker/mysql/init.sql @@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS `contacts` ( `phone` VARCHAR(30), `subject` VARCHAR(255), `message` TEXT NOT NULL, + `is_read` TINYINT(1) NOT NULL DEFAULT 0, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; diff --git a/www/includes/db.php b/www/includes/db.php new file mode 100644 index 0000000..af90daf --- /dev/null +++ b/www/includes/db.php @@ -0,0 +1,35 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ] + ); + + // Auto-migrate: add is_read if the DB was initialised before this column existed + $col_exists = $pdo->query( + "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'contacts' AND COLUMN_NAME = 'is_read'" + )->fetchColumn(); + + if (!$col_exists) { + $pdo->exec("ALTER TABLE contacts ADD COLUMN is_read TINYINT(1) NOT NULL DEFAULT 0"); + } + + return $pdo; +} diff --git a/www/index.php b/www/index.php index 8ef1c20..811d17a 100644 --- a/www/index.php +++ b/www/index.php @@ -2,6 +2,12 @@ // Simple front controller — expand routing here later $path = trim($_GET['path'] ?? '', '/'); +// Staff inbox — self-contained, no public header/footer +if ($path === 'staff-portal') { + require __DIR__ . '/pages/admin-inbox.php'; + exit; +} + // Map paths to page includes $pages = [ '' => 'pages/home.php', diff --git a/www/pages/admin-inbox.php b/www/pages/admin-inbox.php new file mode 100644 index 0000000..e23455c --- /dev/null +++ b/www/pages/admin-inbox.php @@ -0,0 +1,239 @@ + + + + + + + Staff Portal – ALWISP + + + +
+

ALWISP Staff Portal

+ +
+ +
+ + + +
+
+ + + Database connection failed: ' . htmlspecialchars($e->getMessage()) . '

'); +} + +// Mark a single message as read +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['mark_read'])) { + $db->prepare("UPDATE contacts SET is_read = 1 WHERE id = ?")->execute([(int)$_POST['mark_read']]); + header('Location: /staff-portal'); + exit; +} + +// Mark all as read +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['mark_all_read'])) { + $db->exec("UPDATE contacts SET is_read = 1"); + header('Location: /staff-portal'); + exit; +} + +// ── Fetch messages ──────────────────────────────────────────────────────── +$messages = $db->query("SELECT * FROM contacts ORDER BY created_at DESC")->fetchAll(); +$unread_count = (int)$db->query("SELECT COUNT(*) FROM contacts WHERE is_read = 0")->fetchColumn(); + +$subject_labels = [ + 'new-service' => 'New Service Inquiry', + 'support' => 'Technical Support', + 'billing' => 'Billing Question', + 'coverage' => 'Coverage Question', + 'other' => 'Other', +]; + +function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES); } + +?> + + + + + + <?= $unread_count ? "($unread_count) " : '' ?>Inbox – ALWISP Staff + + + + +
+
ALWISP Staff Portal
+
+ 0): ?> +
+ +
+ + Sign out +
+
+ +
+
+ unread + total submission +
+ + +
+
📭
+

No messages yet. They'll show up here once someone submits the contact form.

+
+ + + +
+
+ + + + + + + + + +
+
+

+
+ +
+ + +
+ + + diff --git a/www/pages/contact.php b/www/pages/contact.php index 76bffe7..a864afb 100644 --- a/www/pages/contact.php +++ b/www/pages/contact.php @@ -1,21 +1,33 @@ prepare( + "INSERT INTO contacts (name, email, phone, subject, message) + VALUES (?, ?, ?, ?, ?)" + ); + $stmt->execute([$name, $email, $phone, $subject, $message]); + $success = true; + } catch (PDOException $e) { + $errors[] = 'Sorry, we could not save your message right now. Please try again.'; + } } } ?>