Add flash messages.

This commit is contained in:
Greg Sarjeant 2025-06-16 16:47:35 -04:00
parent 0b4348f14b
commit 856677659e
10 changed files with 156 additions and 18 deletions

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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());
}
}

View File

@ -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();

View 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();
}
}

View File

@ -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>

View File

@ -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"

View File

@ -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)): ?>

View File

@ -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>