diff --git a/html-router/assets/toast.js b/html-router/assets/toast.js
new file mode 100644
index 0000000..bed070a
--- /dev/null
+++ b/html-router/assets/toast.js
@@ -0,0 +1,56 @@
+ document.addEventListener("DOMContentLoaded", function () {
+ window.show_toast = function (description, type = 'info', title = null) {
+ const container = document.getElementById('toast-container');
+ if (!container) {
+ console.error("Toast container not found!");
+ return;
+ }
+ const alert = document.createElement('div');
+ // Base classes for the alert
+ alert.className = `alert alert-${type} mt-2 shadow-md flex flex-col text-start`;
+
+ // Build inner HTML based on whether title is provided
+ let innerHTML = '';
+ if (title) {
+ innerHTML += `
${title}
`; // Title element
+ innerHTML += `${description}
`; // Description element
+ } else {
+ // Structure without title
+ innerHTML += `${description}`;
+ }
+
+ alert.innerHTML = innerHTML;
+ container.appendChild(alert);
+
+ // Auto-remove after a delay
+ setTimeout(() => {
+ // Optional: Add fade-out effect
+ alert.style.opacity = '0';
+ alert.style.transition = 'opacity 0.5s ease-out';
+ setTimeout(() => alert.remove(), 500); // Remove after fade
+ }, 3000); // Start fade-out after 3 seconds
+ };
+
+ document.body.addEventListener('toast', function (event) {
+ // Extract data from the event detail, matching the Rust payload
+ const detail = event.detail;
+ if (detail && detail.description) {
+ const description = detail.description;
+ const type = detail.type || 'info'; // Default to 'info'
+ const title = detail.title || null; // Get title, default to null if missing
+
+ // Call the updated show_toast function
+ window.show_toast(description, type, title);
+ } else {
+ console.warn("Received toast event without detail.description", detail);
+ // Fallback toast if description is missing
+ window.show_toast("An event occurred, but details are missing.", "warning");
+ }
+ });
+
+ document.body.addEventListener('htmx:beforeRequest', function (evt) {
+ const container = document.getElementById('toast-container');
+ if (container) container.innerHTML = '';
+ });
+ })
+
diff --git a/html-router/src/middlewares/response_middleware.rs b/html-router/src/middlewares/response_middleware.rs
index 203af37..1e59fc3 100644
--- a/html-router/src/middlewares/response_middleware.rs
+++ b/html-router/src/middlewares/response_middleware.rs
@@ -7,6 +7,7 @@ use axum::{
use common::{error::AppError, utils::template_engine::ProvidesTemplateEngine};
use minijinja::{context, Value};
use serde::Serialize;
+use serde_json::json;
use tracing::error;
#[derive(Clone)]
@@ -42,11 +43,10 @@ impl TemplateResponse {
}
}
- pub fn error(status: StatusCode, title: &str, error: &str, description: &str) -> Self {
+ pub fn error(status: StatusCode, title: &str, description: &str) -> Self {
let ctx = context! {
status_code => status.as_u16(),
title => title,
- error => error,
description => description
};
Self {
@@ -59,7 +59,6 @@ impl TemplateResponse {
Self::error(
StatusCode::NOT_FOUND,
"Page Not Found",
- "Not Found",
"The page you're looking for doesn't exist or was removed.",
)
}
@@ -68,7 +67,6 @@ impl TemplateResponse {
Self::error(
StatusCode::INTERNAL_SERVER_ERROR,
"Internal Server Error",
- "Internal Server Error",
"Something went wrong on our end.",
)
}
@@ -77,18 +75,12 @@ impl TemplateResponse {
Self::error(
StatusCode::UNAUTHORIZED,
"Unauthorized",
- "Access Denied",
"You need to be logged in to access this page.",
)
}
pub fn bad_request(message: &str) -> Self {
- Self::error(
- StatusCode::BAD_REQUEST,
- "Bad Request",
- "Bad Request",
- message,
- )
+ Self::error(StatusCode::BAD_REQUEST, "Bad Request", message)
}
pub fn redirect(path: impl Into) -> Self {
@@ -118,7 +110,7 @@ where
Ok(html) => Html(html).into_response(),
Err(e) => {
error!("Failed to render template '{}': {:?}", name, e);
- (StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
+ (StatusCode::INTERNAL_SERVER_ERROR, Html(fallback_error())).into_response()
}
}
}
@@ -127,18 +119,48 @@ where
Ok(html) => Html(html).into_response(),
Err(e) => {
error!("Failed to render block '{}/{}': {:?}", template, block, e);
- (StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
+ (StatusCode::INTERNAL_SERVER_ERROR, Html(fallback_error())).into_response()
}
}
}
- TemplateKind::Error(status) => {
- match template_engine.render("errors/error.html", &template_response.context) {
- Ok(html) => (*status, Html(html)).into_response(),
- Err(e) => {
- error!("Failed to render error template: {:?}", e);
- (*status, fallback_error()).into_response()
+ TemplateKind::Error(_status) => {
+ // Extract title and description from context
+ let title = template_response
+ .context
+ .get_attr("title")
+ .ok()
+ .and_then(|v| v.as_str().map(|s| s.to_string()))
+ .unwrap_or_else(|| "Error".to_string()); // Fallback title
+ let description = template_response
+ .context
+ .get_attr("description")
+ .ok()
+ .and_then(|v| v.as_str().map(|s| s.to_string()))
+ .unwrap_or_else(|| "An error occurred.".to_string()); // Fallback desc
+
+ let trigger_payload = json!({
+ "toast": {
+ "title": title,
+ "description": description,
+ "type": "error"
}
- }
+ });
+
+ // Convert payload to string
+ let trigger_value = serde_json::to_string(&trigger_payload)
+ .unwrap_or_else(|e| {
+ error!("Failed to serialize HX-Trigger payload: {}", e);
+ // Fallback trigger if serialization fails
+ r#"{"toast":{"title":"Error","description":"An unexpected error occurred.", "type":"error"}}"#.to_string()
+ });
+
+ // Return 204 No Content with HX-Trigger header
+ (
+ StatusCode::NO_CONTENT,
+ [(axum_htmx::HX_TRIGGER, trigger_value)],
+ "", // Empty body for 204
+ )
+ .into_response()
}
TemplateKind::Redirect(path) => {
(StatusCode::OK, [(axum_htmx::HX_REDIRECT, path.clone())], "").into_response()
diff --git a/html-router/templates/auth/account_settings.html b/html-router/templates/auth/account_settings.html
index 11c493a..6512b26 100644
--- a/html-router/templates/auth/account_settings.html
+++ b/html-router/templates/auth/account_settings.html
@@ -40,44 +40,15 @@
diff --git a/html-router/templates/body_base.html b/html-router/templates/body_base.html
index 482c8c9..2497dc5 100644
--- a/html-router/templates/body_base.html
+++ b/html-router/templates/body_base.html
@@ -10,6 +10,7 @@
{% block main %}{% endblock %}
+