//! The Askama entry point for the described "sign out a device" buttons. //! //! Shape 8 (`b279b9eb`), through the glue-module ruling `27d5e5b8`: a described //! fragment inside an Askama page, at the granularity of one act, in a sibling //! of `widgets/` rather than a member of it. The reasoning is in //! [`super::widgets`]; [`super::export_act`] was the first thing it bought. //! //! # What the description says that the markup did not //! //! That revoking a session destroys something. The template drew both buttons //! `btn-secondary`, so the page did not mark the destructive act even to the //! stylesheet, while writing the question as an attribute only a browser reads. //! Said here, a terminal host asks in its own way and no renderer can forget to //! ask. //! //! # Why the region is named, and why there are two functions //! //! `DELETE /api/users/me/sessions{,/id}` is a plain API route the description //! layer does not serve: it answers the whole rebuilt pane, so the control has //! to say where that lands. See [`Action::replaces`], which is exactly this //! case rather than the misuse it warns about. //! //! The bulk act is not the row act with a different address. It asks a //! different question -- one device against every other one -- and a prompt is //! part of what an act is, so reusing the row's wording would be describing the //! wrong call. Two acts, one region. //! //! Ids arrive as `impl Display` rather than `&str`: the caller is Askama and //! the server's ids are typed newtypes over `Uuid`, so asking for a string //! would push a `to_string()` into every call site to say what `format!` //! already says here. use std::fmt; use makeover_layout::Tone; use quasi_router::screen::Act; use quasi_router::{Action, Node}; /// The list this pane redraws into, which both acts replace. const REGION: &str = "sessions-list"; /// Sign out one device, by session id. #[must_use] pub fn html(session_id: impl fmt::Display) -> String { fragment( Act::new( "Sign out", Action::delete(format!("/api/users/me/sessions/{session_id}")).replacing(REGION), ) .tone(Tone::Danger) .confirm("Sign out this device?"), ) } /// Sign out every device except the one reading this. #[must_use] pub fn all_others_html() -> String { fragment( Act::new( "Sign out all other devices", Action::delete("/api/users/me/sessions").replacing(REGION), ) .tone(Tone::Danger) .confirm("Sign out all other devices?"), ) } /// No shell and no region: this lands inside a document Askama has already /// built, which is what every glue module here does. fn fragment(act: Act) -> String { use quasi_axum::Serves as _; quasi_webview::Webview::new().fragment(&Node::Act(act)) } #[cfg(test)] mod tests { /// Where it goes, what it redraws, and what to ask first. #[test] fn a_sign_out_names_its_route_its_region_and_its_prompt() { let html = super::html("7"); assert!( html.contains(r#"hx-delete="/api/users/me/sessions/7""#), "{html}" ); assert!(html.contains(r##"hx-target="#sessions-list""##), "{html}"); assert!( html.contains(r#"hx-confirm="Sign out this device?""#), "{html}" ); } /// Revoking a session destroys something, and the template said so to /// nobody: both buttons were `btn-secondary`. #[test] fn signing_out_says_it_is_destructive() { assert!(super::html("7").contains(r#"data-tone="danger""#)); assert!(super::all_others_html().contains(r#"data-tone="danger""#)); } /// The bulk act asks its own question. Reusing the row's wording would be /// asking about one device before revoking all of them. #[test] fn the_bulk_act_asks_about_the_set_it_acts_on() { let html = super::all_others_html(); assert!( html.contains(r#"hx-delete="/api/users/me/sessions""#), "{html}" ); assert!( html.contains(r#"hx-confirm="Sign out all other devices?""#), "{html}" ); } }