brewlog/src/application/routes/app/bags.rs
Jon Seager 4fd72d654a
feat(bags): add bag detail page, extract shared detail page macros
- Create /bags/:id detail page with coffee, roaster, map, and bag info cards
- Extract shared template macros into detail_cards.html (coffee_card, roaster_card, map_with_legend, share_button)
- Extract build_coffee_info() and build_roaster_info() view model helpers
- Refactor brew.html and cup.html to use shared macros
- Make bag cards on homepage clickable, linking to detail page
- Add Close Bag and Delete actions on bag detail page
- Unify homepage card styling (bg-surface, hover:border-accent/40)
- Update CLAUDE.md with detail page patterns
2026-02-08 16:43:04 +00:00

50 lines
1.4 KiB
Rust

use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use tower_cookies::Cookies;
use crate::application::errors::map_app_error;
use crate::application::routes::render_html;
use crate::application::state::AppState;
use crate::domain::ids::BagId;
use crate::presentation::web::templates::BagDetailTemplate;
use crate::presentation::web::views::BagDetailView;
#[tracing::instrument(skip(state, cookies))]
pub(crate) async fn bag_detail_page(
State(state): State<AppState>,
cookies: Cookies,
Path(id): Path<BagId>,
) -> Result<Response, StatusCode> {
let is_authenticated = crate::application::routes::is_authenticated(&state, &cookies).await;
let bag = state
.bag_repo
.get_with_roast(id)
.await
.map_err(|e| map_app_error(e.into()))?;
let roast = state
.roast_repo
.get(bag.bag.roast_id)
.await
.map_err(|e| map_app_error(e.into()))?;
let roaster = state
.roaster_repo
.get(roast.roaster_id)
.await
.map_err(|e| map_app_error(e.into()))?;
let view = BagDetailView::from_parts(bag, &roast, &roaster);
let template = BagDetailTemplate {
nav_active: "",
is_authenticated,
version_info: &crate::VERSION_INFO,
base_url: crate::base_url(),
bag: view,
};
render_html(template).map(IntoResponse::into_response)
}