use dioxus::prelude::*;
use super::blog_meta::listing_tags;
use super::{BlogCard, TagFilter};
use crate::components::managed_head::{ManagedPageHead, NotFoundMeta};
use crate::components::seo::join_site_url;
use crate::{BlogCategory, BlogContext, BlogRegistry};
#[component]
pub fn BlogCategoryPage(slug: String, #[props(default = 1)] page: usize) -> Element {
let registry = use_context::<&'static BlogRegistry>();
let ctx = use_context::<BlogContext>();
let Some((category, posts)) = registry
.get_category(&slug)
.zip(registry.category_posts_page(&slug, page))
else {
return rsx! {
NotFoundMeta { title: "Category page not found", auto_meta: ctx.auto_meta }
main { class: "max-w-6xl mx-auto px-4 py-12",
h1 { class: "text-4xl font-bold mb-4", "Category page not found" }
p { class: "text-base-content/70 mb-8", "This topic or page doesn't exist." }
Link { to: NavigationTarget::Internal(ctx.base_path), class: "btn btn-primary", "Back to Blog" }
}
};
};
let total_pages = registry.total_pages_for_tag(&category.tag);
let count = registry.tag_count(&category.tag);
rsx! {
CategoryMeta { category: category.clone(), page }
main { class: "dk-blog-category max-w-6xl mx-auto px-4 py-12",
header { class: "max-w-3xl mb-8",
Link { to: NavigationTarget::Internal(ctx.base_path), class: "text-sm text-base-content/60 hover:text-primary", "Blog" }
h1 { class: "text-4xl font-bold tracking-tight mt-4 mb-3", "{category.title}" }
p { class: "text-lg text-base-content/60 leading-relaxed", "{category.description}" }
p { class: "text-sm text-base-content/50 mt-4", "{count} articles" }
if let Some(image) = &category.image {
img { src: "{image}", alt: "", class: "w-full rounded-xl mt-6", loading: "lazy" }
}
}
div { class: "mb-8", TagFilter {} }
div { class: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6",
for post in posts {
BlogCard { key: "{post.slug}", post: post.clone() }
}
}
if total_pages > 1 {
nav { class: "flex flex-wrap items-center justify-center gap-2 mt-12", aria_label: "Category pagination",
if page > 1 {
Link { to: NavigationTarget::Internal(registry.category_url(&slug, page - 1).unwrap()), class: "btn btn-ghost btn-sm", "Previous" }
}
span { class: "text-sm text-base-content/60", "Page {page} of {total_pages}" }
if page < total_pages {
Link { to: NavigationTarget::Internal(registry.category_url(&slug, page + 1).unwrap()), class: "btn btn-ghost btn-sm", "Next" }
}
}
}
}
}
}
#[component]
fn CategoryMeta(category: BlogCategory, page: usize) -> Element {
let ctx = use_context::<BlogContext>();
let registry = use_context::<&'static BlogRegistry>();
if !ctx.auto_meta {
return rsx! {};
}
let title = if page == 1 {
category.title.clone()
} else {
format!("{} — Page {page}", category.title)
};
let description = category.description;
let path = registry.category_url(&category.slug, page).unwrap();
let canonical = ctx
.site_url
.as_deref()
.map(|site| join_site_url(site, &path, ""));
let image = category.image.map(|image| {
if image.starts_with('/') && !image.starts_with("//") {
ctx.site_url
.as_deref()
.map(|site| join_site_url(site, &image, ""))
.unwrap_or(image)
} else {
image
}
});
let tags = listing_tags(&title, &description, canonical.as_deref(), image.as_deref());
rsx! { ManagedPageHead { title, tags } }
}