From 612a4bf790be57c272e261361c30bc919d20566f Mon Sep 17 00:00:00 2001
From: Hosteroid
Date: Sun, 1 Feb 2026 12:30:16 +0200
Subject: [PATCH] Use POST for destructive actions & mobile UI tweaks
Require POST and CSRF verification for destructive endpoints (profile delete, notification delete, clear-all) and update routes accordingly. Replace GET-based delete links with POST forms (including csrf_field()) and add hidden form submission for "clear all" and account deletion via JS. Add server-side request method checks and verifyCsrf() calls in NotificationController and ProfileController. Improve mobile UX: add sidebar overlay, open/close controls (including swipe-to-close), close button, prevent body scroll when sidebar open, responsive search placeholder and adjusted search/top-nav styling, and minor layout tweaks (truncate app name, adjust notification dropdown width). Also minor whitespace/formatting cleanups.
---
app/Controllers/NotificationController.php | 18 ++++
app/Controllers/ProfileController.php | 17 +++-
app/Views/layout/base.php | 96 +++++++++++++++++++++-
app/Views/layout/sidebar.php | 12 ++-
app/Views/layout/top-nav.php | 14 ++--
app/Views/notifications/index.php | 16 +++-
app/Views/profile/index.php | 13 +--
routes/web.php | 6 +-
8 files changed, 163 insertions(+), 29 deletions(-)
diff --git a/app/Controllers/NotificationController.php b/app/Controllers/NotificationController.php
index 68df855..b7113ac 100644
--- a/app/Controllers/NotificationController.php
+++ b/app/Controllers/NotificationController.php
@@ -110,6 +110,15 @@ class NotificationController extends Controller
*/
public function delete($params = [])
{
+ // Ensure POST method
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+ $this->redirect('/notifications');
+ return;
+ }
+
+ // CSRF Protection
+ $this->verifyCsrf('/notifications');
+
$userId = Auth::id();
$notificationId = (int)($params['id'] ?? 0);
@@ -129,6 +138,15 @@ class NotificationController extends Controller
*/
public function clearAll()
{
+ // Ensure POST method
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+ $this->redirect('/notifications');
+ return;
+ }
+
+ // CSRF Protection
+ $this->verifyCsrf('/notifications');
+
$userId = Auth::id();
$this->notificationModel->clearAll($userId);
$_SESSION['success'] = 'All notifications cleared';
diff --git a/app/Controllers/ProfileController.php b/app/Controllers/ProfileController.php
index bf379e8..e0750d1 100644
--- a/app/Controllers/ProfileController.php
+++ b/app/Controllers/ProfileController.php
@@ -232,6 +232,15 @@ class ProfileController extends Controller
*/
public function delete()
{
+ // Ensure POST method
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+ $this->redirect('/profile');
+ return;
+ }
+
+ // CSRF Protection
+ $this->verifyCsrf('/profile');
+
$userId = Auth::id();
$user = $this->userModel->find($userId);
@@ -243,14 +252,14 @@ class ProfileController extends Controller
}
// Delete user (cascade will handle related records)
- $this->userModel->delete($userId);
+ $this->userModel->delete($userId);
// Logout
- session_destroy();
- session_start();
+ session_destroy();
+ session_start();
$_SESSION['success'] = 'Your account has been deleted';
- $this->redirect('/login');
+ $this->redirect('/login');
}
/**
diff --git a/app/Views/layout/base.php b/app/Views/layout/base.php
index 5fd240a..4dfed8a 100644
--- a/app/Views/layout/base.php
+++ b/app/Views/layout/base.php
@@ -93,7 +93,22 @@ if (!isset($appName)) {
transition: transform 0.3s ease-in-out;
}
- @media (max-width: 768px) {
+ /* Mobile sidebar overlay */
+ .sidebar-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 25;
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
+ }
+ .sidebar-overlay.show {
+ opacity: 1;
+ visibility: visible;
+ }
+
+ @media (max-width: 767px) {
.sidebar {
transform: translateX(-100%);
}
@@ -120,12 +135,27 @@ if (!isset($appName)) {
background: #374151;
border-left: 4px solid #4A90E2;
}
+
+ /* Mobile-friendly dropdown widths */
+ @media (max-width: 480px) {
+ #notificationsDropdown {
+ width: calc(100vw - 2rem);
+ right: -0.5rem;
+ }
+ #userDropdown {
+ width: calc(100vw - 2rem);
+ right: -0.5rem;
+ }
+ }
+
+
+
@@ -145,8 +175,70 @@ if (!isset($appName)) {
diff --git a/app/Views/profile/index.php b/app/Views/profile/index.php
index 724f58e..c1f0f88 100644
--- a/app/Views/profile/index.php
+++ b/app/Views/profile/index.php
@@ -644,10 +644,13 @@ $avatar = \App\Helpers\AvatarHelper::getAvatar($user, 80);
This action cannot be undone
-
+
@@ -734,7 +737,7 @@ document.addEventListener('DOMContentLoaded', function() {
function confirmDelete() {
if (confirm('Are you absolutely sure you want to delete your account?\n\nThis action is PERMANENT and cannot be undone!')) {
if (confirm('FINAL WARNING: This will permanently delete all your data.\n\nClick OK to proceed.')) {
- window.location.href = '/profile/delete';
+ document.getElementById('deleteAccountForm').submit();
}
}
}
diff --git a/routes/web.php b/routes/web.php
index 4bb5cf0..43ea567 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -135,7 +135,7 @@ $router->post('/settings/toggle-isolation', [SettingsController::class, 'toggleI
$router->get('/profile', [ProfileController::class, 'index']);
$router->post('/profile/update', [ProfileController::class, 'update']);
$router->post('/profile/change-password', [ProfileController::class, 'changePassword']);
-$router->get('/profile/delete', [ProfileController::class, 'delete']);
+$router->post('/profile/delete', [ProfileController::class, 'delete']);
$router->get('/profile/resend-verification', [ProfileController::class, 'resendVerification']);
$router->post('/profile/logout-other-sessions', [ProfileController::class, 'logoutOtherSessions']);
$router->post('/profile/logout-session/{sessionId}', [ProfileController::class, 'logoutSession']);
@@ -154,8 +154,8 @@ $router->post('/2fa/regenerate-backup-codes', [TwoFactorController::class, 'rege
$router->get('/notifications', [NotificationController::class, 'index']);
$router->get('/notifications/{id}/mark-read', [NotificationController::class, 'markAsRead']);
$router->get('/notifications/mark-all-read', [NotificationController::class, 'markAllAsRead']);
-$router->get('/notifications/{id}/delete', [NotificationController::class, 'delete']);
-$router->get('/notifications/clear-all', [NotificationController::class, 'clearAll']);
+$router->post('/notifications/{id}/delete', [NotificationController::class, 'delete']);
+$router->post('/notifications/clear-all', [NotificationController::class, 'clearAll']);
$router->get('/api/notifications/unread-count', [NotificationController::class, 'getUnreadCount']);
$router->get('/api/notifications/recent', [NotificationController::class, 'getRecent']);