//! Multi-step creation wizards for projects and items. //! //! Each wizard is a full page with a sidebar step indicator + content area. //! Steps are HTMX partials swapped into `#wizard-step`. Each step form POSTs //! to save, and the server responds with the next step partial. The DB record //! IS the wizard state; step 1 creates it, subsequent steps update it. pub mod item; pub mod project; use axum::routing::get; use crate::{ csrf::{post_csrf, with_csrf, CsrfRouter}, AppState, }; /// Step navigation helpers shared by both wizards. pub fn next_step<'a>(steps: &'a [&str], current: &str) -> Option<&'a str> { steps .iter() .position(|&s| s == current) .and_then(|i| steps.get(i + 1)) .copied() } fn step_index(steps: &[&str], current: &str) -> Option { steps.iter().position(|&s| s == current) } use crate::templates::StepNavItem; /// Build the list of step display info for the sidebar nav. pub fn build_step_nav( steps: &[&'static str], labels: &[&'static str], current: &str, ) -> Vec { let current_idx = step_index(steps, current).unwrap_or(0); steps .iter() .zip(labels.iter()) .enumerate() .map(|(i, (&name, &label))| StepNavItem { name, label, state: if i < current_idx { "completed" } else if i == current_idx { "active" } else { "pending" }, }) .collect() } /// Register all wizard routes. pub fn wizard_routes() -> CsrfRouter { CsrfRouter::new() // Project wizard .route_get("/dashboard/new-project", get(project::wizard_page)) .route( "/dashboard/new-project/step/basics", post_csrf(project::step_basics_create), ) .route( "/dashboard/new-project/{slug}/step/{step}", with_csrf(get(project::step_load).post(project::step_save)), ) // Item wizard .route_get( "/dashboard/project/{slug}/new-item", get(item::wizard_page), ) .route( "/dashboard/project/{slug}/new-item/step/type", post_csrf(item::step_type_create), ) .route( "/dashboard/project/{slug}/new-item/{id}/step/{step}", with_csrf(get(item::step_load).post(item::step_save)), ) }