use axum::http::StatusCode;
use quokka::{
extract::Extensions,
handler::html::TemplateDataLoader,
state::{FromState, ProvideState, Templating},
};
pub use crate::data::navigation::*;
pub use crate::data::sidebar_widget::*;
use crate::{data::Toast, state::AdminState};
pub const ADMIN_ERROR_TEMPLATE: &str = "quokka-admin/page/error/error.html.hbs";
#[derive(Clone, FromState)]
pub struct AdminPageLoader {
templating: Templating,
#[from_state(
bounds = "State: ProvideState<AdminState<State>> + 'static",
builder = ProvideState::<AdminState<_>>::provide(state).get_navigation(),
)]
navigation: Vec<AdminNavigationGroup>,
#[from_state(
bounds = "State: ProvideState<AdminState<State>> + 'static",
builder = ProvideState::<AdminState<_>>::provide(state).sidebar_widgets,
)]
sidebar_widgets: Vec<AdminSidebarWidget>,
}
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct AdminErrorMessage {
pub message: String,
pub status_code: u16,
}
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct AdminPageData<P> {
pub title: String,
pub subtitle: Option<String>,
pub navigation: Vec<AdminNavigationGroup>,
pub sidebar_widgets: Vec<AdminSidebarWidget>,
pub toasts: Vec<Toast>,
#[serde(flatten)]
pub page: P,
pub error: Option<AdminErrorMessage>,
}
impl<S: Send + Sync + 'static> TemplateDataLoader<S> for AdminPageLoader {
type Args = Extensions<Toast>;
type Data = AdminPageData<()>;
#[tracing::instrument(
skip_all,
target = "quokka_admin::service::page_loader::admin_page_loader::AdminPageLoader"
)]
async fn load_data(&self, Extensions(toasts): Self::Args) -> quokka::Result<Self::Data> {
Ok(AdminPageData {
title: "Quokka Admin".to_string(),
navigation: self.navigation.clone(),
sidebar_widgets: self.sidebar_widgets.clone(),
toasts,
..Default::default()
})
}
#[tracing::instrument(
skip_all,
target = "quokka_admin::service::page_loader::admin_page_loader::AdminPageLoader"
)]
async fn render_error(&self, error: quokka::Error) -> impl axum::response::IntoResponse {
tracing::error!(?error, "Caught error in AdminPageLoader::render_error");
let mut message = error.message;
if error.status_code == 403 {
message = "Permission denied".to_string();
}
(
StatusCode::from_u16(error.status_code)
.unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR),
axum::response::Html(self.templating.render(
ADMIN_ERROR_TEMPLATE,
&AdminPageData::<()> {
title: "Quokka Admin".to_string(),
navigation: self.navigation.clone(),
sidebar_widgets: self.sidebar_widgets.clone(),
error: Some(AdminErrorMessage {
message,
status_code: error.status_code,
}),
..Default::default()
},
)),
)
}
}
impl<P> AdminPageData<P> {
pub fn page<P2>(self, page: P2) -> AdminPageData<P2> {
AdminPageData {
page,
title: self.title,
subtitle: self.subtitle,
navigation: self.navigation,
sidebar_widgets: self.sidebar_widgets,
toasts: self.toasts,
error: self.error,
}
}
pub fn message(mut self, toast: impl Into<Toast>) -> Self {
self.toasts.push(toast.into());
self
}
}