Add flash messages.
This commit is contained in:
		
							parent
							
								
									0b4348f14b
								
							
						
					
					
						commit
						856677659e
					
				| @ -38,6 +38,23 @@ | ||||
|     /* Shadow colors */ | ||||
|     --shadow-primary: rgba(66, 153, 225, 0.1); | ||||
|     --shadow-primary-strong: rgba(66, 153, 225, 0.3); | ||||
| 
 | ||||
|     /* Flash colors */ | ||||
|     --color-flash-success: #155724; | ||||
|     --color-flash-success-bg: #d4edda; | ||||
|     --color-flash-success-border-left: #28a745; | ||||
| 
 | ||||
|     --color-flash-error: #721c24; | ||||
|     --color-flash-error-bg: #f8d7da; | ||||
|     --color-flash-error-border-left: #dc3545; | ||||
| 
 | ||||
|     --color-flash-warning: #856404; | ||||
|     --color-flash-warning-bg: #fff3cd; | ||||
|     --color-flash-warning-border-left: #ffc107; | ||||
| 
 | ||||
|     --color-flash-info: #0c5460; | ||||
|     --color-flash-info-bg: #d1ecf1; | ||||
|     --color-flash-info-border-left: #17a2b8; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
| @ -227,6 +244,46 @@ label.description { | ||||
|     gap: 0.5em; | ||||
| } | ||||
| 
 | ||||
| .flash-messages { | ||||
|     background: white; | ||||
|     margin-top: 10px; | ||||
|     padding: 15px; | ||||
|     border-radius: 8px; | ||||
|     box-shadow: 0 2px 10px rgba(0,0,0,0.1); | ||||
| } | ||||
|          | ||||
| .flash-message { | ||||
|     padding: 12px 16px; | ||||
|     margin: 5px 0; | ||||
|     border-radius: 4px; | ||||
|     border-left: 4px solid; | ||||
|     font-weight: 500; | ||||
| } | ||||
| 
 | ||||
| .flash-success { | ||||
|     background-color: var(--color-flash-success-bg); | ||||
|     border-left-color: var(--color-flash-success-border-left); | ||||
|     color: var(--color-flash-success); | ||||
| } | ||||
| 
 | ||||
| .flash-error { | ||||
|     background-color: var(--color-flash-error-bg); | ||||
|     border-left-color: var(--color-flash-error-border-left); | ||||
|     color: var(--color-flash-error); | ||||
| } | ||||
| 
 | ||||
| .flash-warning { | ||||
|     background-color: var(--color-flash-warning-bg); | ||||
|     border-left-color: var(--color-flash-warning-border-left); | ||||
|     color: var(--color-flash-warning); | ||||
| } | ||||
| 
 | ||||
| .flash-info { | ||||
|     background-color: var(--color-flash-info-bg); | ||||
|     border-left-color: var(--color-flash-info-border-left); | ||||
|     color: var(--color-flash-info); | ||||
| } | ||||
|          | ||||
| .fieldset-items { | ||||
|     margin-bottom: 14px; | ||||
|     display: grid; | ||||
|  | ||||
| @ -100,11 +100,11 @@ class AdminController extends Controller { | ||||
|             } | ||||
|          | ||||
|             // If a password was sent, make sure it matches the confirmation
 | ||||
|             if ($password && !($password = $confirmPassword)){ | ||||
|             if ($password && !($password === $confirmPassword)){ | ||||
|                 $errors[] = "Passwords do not match"; | ||||
|             } | ||||
|          | ||||
|             // TODO: Actually handle errors
 | ||||
|             // Validation complete
 | ||||
|             if (empty($errors)) { | ||||
|                 // Update site settings
 | ||||
|                 $config->siteTitle = $siteTitle; | ||||
| @ -114,6 +114,7 @@ class AdminController extends Controller { | ||||
|                 $config->itemsPerPage = $itemsPerPage; | ||||
|              | ||||
|                 // Save site settings and reload config from database
 | ||||
|                 // TODO - raise and handle exception on failure
 | ||||
|                 $config = $config->save(); | ||||
|              | ||||
|                 // Update user profile
 | ||||
| @ -123,19 +124,24 @@ class AdminController extends Controller { | ||||
|                 $user->website = $website; | ||||
|              | ||||
|                 // Save user profile and reload user from database
 | ||||
|                 // TODO - raise and handle exception on failure
 | ||||
|                 $user = $user->save(); | ||||
|              | ||||
|                 // Update the password if one was sent
 | ||||
|                 // TODO - raise and handle exception on failure
 | ||||
|                 if($password){ | ||||
|                     $user->set_password($password); | ||||
|                 } | ||||
| 
 | ||||
|                 Session::setFlashMessage('success', 'Settings updated'); | ||||
|             } else { | ||||
|                 echo implode(",", $errors); | ||||
|                 exit; | ||||
|                 foreach($errors as $error){ | ||||
|                     Session::setFlashMessage('error', $error); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         header('Location: ' . $config->basePath . 'admin'); | ||||
|         header('Location: ' . $_SERVER['PHP_SELF']); | ||||
|         exit; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,6 +13,15 @@ class Controller { | ||||
|             throw new RuntimeException("Template not found: $childTemplatePath"); | ||||
|         } | ||||
| 
 | ||||
|         // always check for flash messages and add them if they exist
 | ||||
|         if (Session::hasFlashMessages()){ | ||||
|             $flashMessages = Session::getFlashMessages(); | ||||
|             $flashView = new FlashView(); | ||||
|             $flashSection = $flashView->renderFlashSection($flashMessages); | ||||
| 
 | ||||
|             $vars['flashSection'] = $flashSection; | ||||
|         } | ||||
| 
 | ||||
|         extract($vars, EXTR_SKIP); | ||||
|         include $templatePath; | ||||
|     }   | ||||
|  | ||||
| @ -63,7 +63,8 @@ class CssController extends Controller { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         header('Location: ' . $config->basePath . 'admin/css'); | ||||
|         // redirect after handling to avoid resubmitting form
 | ||||
|         header('Location: ' . $_SERVER['PHP_SELF']); | ||||
|         exit; | ||||
|     } | ||||
| 
 | ||||
| @ -114,6 +115,9 @@ class CssController extends Controller { | ||||
|         // Set the theme back to default
 | ||||
|         $config->cssId = null; | ||||
|         $config = $config->save(); | ||||
| 
 | ||||
|         // Set flash message
 | ||||
|         Session::setFlashMessage('success', 'Theme ' . $cssFilename . ' deleted.'); | ||||
|     } | ||||
| 
 | ||||
|     private function handleSetTheme() { | ||||
| @ -129,6 +133,9 @@ class CssController extends Controller { | ||||
| 
 | ||||
|         // Update the site theme
 | ||||
|         $config = $config->save(); | ||||
| 
 | ||||
|         // Set flash message
 | ||||
|         Session::setFlashMessage('success', 'Theme applied.'); | ||||
|     } | ||||
| 
 | ||||
|     private function handleUpload() { | ||||
| @ -179,11 +186,14 @@ class CssController extends Controller { | ||||
|             // Add upload to database
 | ||||
|             $cssModel = new CssModel(); | ||||
|             $cssModel->save($safeFilename, $description); | ||||
| 
 | ||||
|             return true; | ||||
|          | ||||
|             // Set success flash message
 | ||||
|             Session::setFlashMessage('success', 'Theme uploaded as ' . $safeFilename); | ||||
| 
 | ||||
|         } catch (Exception $e) { | ||||
|            return false;  | ||||
|             // Set error flash message
 | ||||
|             // Todo - don't do a global catch like this. Subclass Exception.
 | ||||
|             Session::setFlashMessage('error', 'Upload exception: ' . $e->getMessage()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -34,6 +34,30 @@ class Session { | ||||
|         return self::isLoggedIn() && self::isValidCsrfToken($token); | ||||
|     } | ||||
| 
 | ||||
|     // valid types are:
 | ||||
|     // - success
 | ||||
|     // - error
 | ||||
|     // - info
 | ||||
|     // - warning
 | ||||
|     public static function setFlashMessage(string $type, string $message): void { | ||||
|         if (!isset($_SESSION['flash'][$type])){ | ||||
|             $_SESSION['flash'][$type] = []; | ||||
|         } | ||||
|         $_SESSION['flash'][$type][] = $message; | ||||
|     } | ||||
| 
 | ||||
|     public static function getFlashMessages(): ?array { | ||||
|         if (isset($_SESSION['flash'])) { | ||||
|             $messages = $_SESSION['flash']; | ||||
|             unset($_SESSION['flash']); | ||||
|             return $messages; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static function hasFlashMessages(): bool { | ||||
|         return isset($_SESSION['flash']) && !empty($_SESSION['flash']); | ||||
|     } | ||||
| 
 | ||||
|     public static function end(): void { | ||||
|         $_SESSION = []; | ||||
|         session_destroy(); | ||||
|  | ||||
							
								
								
									
										21
									
								
								src/View/FlashView/FlashView.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/View/FlashView/FlashView.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| <?php | ||||
| class FlashView { | ||||
|     public function renderFlashSection(array $flashMessages): string { | ||||
|         ob_start(); | ||||
|         ?>
 | ||||
| 
 | ||||
|         <?php if (count($flashMessages) > 0): ?>
 | ||||
|             <div class="flash-messages"> | ||||
|             <?php foreach ($flashMessages as $type => $messages): ?>
 | ||||
|                 <?php foreach ($messages as $message): ?>
 | ||||
|                     <div class="flash-message flash-<?php echo $type; ?>"> | ||||
|                         <?php echo htmlspecialchars($message); ?>
 | ||||
|                     </div> | ||||
|                 <?php endforeach; ?>
 | ||||
|             <?php endforeach; ?>
 | ||||
|             </div> | ||||
|         <?php endif; | ||||
| 
 | ||||
|         return ob_get_clean(); | ||||
|     } | ||||
| } | ||||
| @ -2,6 +2,7 @@ | ||||
| <?php /** @var ConfigModel $config */ ?>
 | ||||
| <?php /** @var UserModel $user */ ?>
 | ||||
| <?php /** @var string $childTemplateFile */ ?>
 | ||||
| <?php /** @var srting $flashSection */ ?>
 | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|     <head> | ||||
| @ -25,6 +26,9 @@ | ||||
|     </head> | ||||
|     <body> | ||||
| <?php include TEMPLATES_DIR . '/partials/navbar.php'?>
 | ||||
| <?php if( isset($flashSection) && !empty($flashSection) ): ?>
 | ||||
|     <?php echo $flashSection; ?>
 | ||||
| <?php endif; ?>
 | ||||
| <?php include TEMPLATES_DIR . '/partials/' . $childTemplateFile?>
 | ||||
|     </body> | ||||
| </html> | ||||
| @ -8,7 +8,7 @@ | ||||
|                 method="post"> | ||||
|                 <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> | ||||
|                 <fieldset> | ||||
|                     <legend>UserModel settings</legend> | ||||
|                     <legend>User settings</legend> | ||||
|                     <div class="fieldset-items"> | ||||
|                         <label>Username <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
| @ -16,10 +16,10 @@ | ||||
|                             value="<?= $user->username ?>" | ||||
|                             required> | ||||
|                         <label>Display name <span class=required>*</span></label> | ||||
|                             <input type="text"  | ||||
|                                 name="display_name" | ||||
|                                 value="<?= $user->displayName ?>" | ||||
|                                 required> | ||||
|                         <input type="text"  | ||||
|                                name="display_name" | ||||
|                                value="<?= $user->displayName ?>" | ||||
|                                required> | ||||
|                         <label>About </label> | ||||
|                         <input type="text" | ||||
|                             name="about" | ||||
|  | ||||
| @ -18,7 +18,9 @@ | ||||
|                                maxlength="40" minlength="1" | ||||
|                                placeholder="describe the mood" | ||||
|                         > | ||||
|                         <div></div> | ||||
|                         <button type="submit" name="action" value="add">Add emoji</button> | ||||
|                     </div> | ||||
|                 </fieldset> | ||||
|             </form> | ||||
| <?php if (!empty($emojiList)): ?>
 | ||||
|  | ||||
| @ -6,8 +6,13 @@ | ||||
|     <p style="color:red"><?=  htmlspecialchars($error) ?></p>
 | ||||
| <?php endif; ?>
 | ||||
|     <form method="post" action="<?= $config->basePath ?>login"> | ||||
|         <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>"> | ||||
|         <label>Username: <input type="text" name="username" required></label><br> | ||||
|         <label>Password: <input type="password" name="password" required></label><br> | ||||
|         <button type="submit" class="submit-btn">Login</button> | ||||
|         <div class="fieldset-items"> | ||||
|             <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>"> | ||||
|             <label for="username">Username:</label> | ||||
|             <input type="text" id="username" name="username" required> | ||||
|             <label for="password">Password:</label> | ||||
|             <input type="password" id="password" name="password" required></label> | ||||
|             <div></div> | ||||
|             <button type="submit" class="submit-btn">Login</button> | ||||
|         </div> | ||||
|     </form> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user