feat(routes): redirect to detail page after creating brews and cups

Update create_brew, create_cup, and submit_checkin to navigate to the
new detail pages instead of reloading or redirecting to home.
This commit is contained in:
Jon Seager 2026-02-08 16:10:11 +00:00
parent b17aaab5f4
commit eaf558a524
No known key found for this signature in database
4 changed files with 34 additions and 14 deletions

View file

@ -254,9 +254,11 @@ pub(crate) async fn create_brew(
info!(brew_id = %enriched.brew.id, "brew created"); info!(brew_id = %enriched.brew.id, "brew created");
state.stats_invalidator.invalidate(); state.stats_invalidator.invalidate();
let detail_url = format!("/brews/{}", enriched.brew.id);
if is_datastar_request(&headers) { if is_datastar_request(&headers) {
// If the request came from a page that has #brew-list, return the updated fragment. // If the request came from a page that has #brew-list, return the updated fragment.
// Otherwise (homepage, timeline, etc.), trigger a page reload instead. // Otherwise (homepage, add page, etc.), redirect to the brew detail page.
let from_brew_page = headers let from_brew_page = headers
.get("referer") .get("referer")
.and_then(|v| v.to_str().ok()) .and_then(|v| v.to_str().ok())
@ -268,8 +270,8 @@ pub(crate) async fn create_brew(
.map_err(ApiError::from) .map_err(ApiError::from)
} else { } else {
use axum::http::header::HeaderValue; use axum::http::header::HeaderValue;
let mut response = let script = format!("<script>window.location.href='{detail_url}'</script>");
axum::response::Html("<script>window.location.reload()</script>").into_response(); let mut response = axum::response::Html(script).into_response();
response response
.headers_mut() .headers_mut()
.insert("datastar-selector", HeaderValue::from_static("body")); .insert("datastar-selector", HeaderValue::from_static("body"));
@ -279,7 +281,7 @@ pub(crate) async fn create_brew(
Ok(response) Ok(response)
} }
} else if matches!(source, PayloadSource::Form) { } else if matches!(source, PayloadSource::Form) {
Ok(Redirect::to("/").into_response()) Ok(Redirect::to(&detail_url).into_response())
} else { } else {
Ok((StatusCode::CREATED, Json(enriched)).into_response()) Ok((StatusCode::CREATED, Json(enriched)).into_response())
} }

View file

@ -89,10 +89,21 @@ pub(crate) async fn submit_checkin(
.await .await
.map_err(AppError::from)?; .map_err(AppError::from)?;
let detail_url = format!("/cups/{}", cup.id);
if is_datastar_request(&headers) { if is_datastar_request(&headers) {
crate::application::routes::support::render_signals_json(&[]).map_err(ApiError::from) use axum::http::header::HeaderValue;
let script = format!("<script>window.location.href='{detail_url}'</script>");
let mut response = axum::response::Html(script).into_response();
response
.headers_mut()
.insert("datastar-selector", HeaderValue::from_static("body"));
response
.headers_mut()
.insert("datastar-mode", HeaderValue::from_static("append"));
Ok(response)
} else if matches!(source, PayloadSource::Form) { } else if matches!(source, PayloadSource::Form) {
Ok(Redirect::to("/").into_response()) Ok(Redirect::to(&detail_url).into_response())
} else { } else {
Ok((StatusCode::CREATED, Json(cup)).into_response()) Ok((StatusCode::CREATED, Json(cup)).into_response())
} }

View file

@ -52,7 +52,7 @@ pub(crate) async fn create_cup(
Query(query): Query<ListQuery>, Query(query): Query<ListQuery>,
payload: FlexiblePayload<NewCup>, payload: FlexiblePayload<NewCup>,
) -> Result<Response, ApiError> { ) -> Result<Response, ApiError> {
let (request, search) = query.into_request_and_search::<CupSortKey>(); let (_request, _search) = query.into_request_and_search::<CupSortKey>();
let (new_cup, source) = payload.into_parts(); let (new_cup, source) = payload.into_parts();
let cup = state let cup = state
@ -64,14 +64,21 @@ pub(crate) async fn create_cup(
info!(cup_id = %cup.id, "cup created"); info!(cup_id = %cup.id, "cup created");
state.stats_invalidator.invalidate(); state.stats_invalidator.invalidate();
let detail_url = format!("/cups/{}", cup.id);
if is_datastar_request(&headers) { if is_datastar_request(&headers) {
render_cup_list_fragment(state, request, search, true) use axum::http::header::HeaderValue;
.await let script = format!("<script>window.location.href='{detail_url}'</script>");
.map_err(ApiError::from) let mut response = axum::response::Html(script).into_response();
response
.headers_mut()
.insert("datastar-selector", HeaderValue::from_static("body"));
response
.headers_mut()
.insert("datastar-mode", HeaderValue::from_static("append"));
Ok(response)
} else if matches!(source, PayloadSource::Form) { } else if matches!(source, PayloadSource::Form) {
let target = Ok(Redirect::to(&detail_url).into_response())
ListNavigator::new(CUP_PAGE_PATH, CUP_FRAGMENT_PATH, request, search).page_href(1);
Ok(Redirect::to(&target).into_response())
} else { } else {
Ok((StatusCode::CREATED, Json(cup)).into_response()) Ok((StatusCode::CREATED, Json(cup)).into_response())
} }

View file

@ -304,7 +304,7 @@ block title %}Brewlog · Check In{% endblock %} {% block head %}
<form <form
data-on:submit="$_submitting = true; $_error = ''; @post('/api/v1/check-in', {contentType: 'form'})" data-on:submit="$_submitting = true; $_error = ''; @post('/api/v1/check-in', {contentType: 'form'})"
data-on:datastar-fetch="if (!$_submitting) return; if (evt.detail.type === 'finished') { window.location.href = '/' } else if (evt.detail.type === 'error') { $_submitting = false; $_error = 'Check-in failed. Please try again.' }" data-on:datastar-fetch="if (!$_submitting) return; if (evt.detail.type === 'error') { $_submitting = false; $_error = 'Check-in failed. Please try again.' }"
> >
<input type="hidden" name="cafe_id" data-attr:value="$_cafeId" /> <input type="hidden" name="cafe_id" data-attr:value="$_cafeId" />
<input type="hidden" name="cafe_name" data-attr:value="$_cafeName" /> <input type="hidden" name="cafe_name" data-attr:value="$_cafeName" />