| 1 |
|
| 2 |
|
| 3 |
use axum::response::{IntoResponse, Response}; |
| 4 |
use tower_sessions::Session; |
| 5 |
|
| 6 |
use crate::{ |
| 7 |
db::{self, ItemType, ProjectFeature}, |
| 8 |
error::{AppError, Result}, |
| 9 |
helpers::get_csrf_token, |
| 10 |
templates::*, |
| 11 |
AppState, |
| 12 |
}; |
| 13 |
|
| 14 |
use super::{build_step_nav, format_price_display, ITEM_LABELS, ITEM_STEPS}; |
| 15 |
|
| 16 |
pub(super) async fn render_step( |
| 17 |
state: &AppState, |
| 18 |
session: &Session, |
| 19 |
_user: &crate::auth::SessionUser, |
| 20 |
project: &db::DbProject, |
| 21 |
item: &db::DbItem, |
| 22 |
step: &str, |
| 23 |
) -> Result<Response> { |
| 24 |
let nav = build_step_nav(ITEM_STEPS, ITEM_LABELS, step); |
| 25 |
let project_slug = project.slug.to_string(); |
| 26 |
let item_id = item.id.to_string(); |
| 27 |
let csrf_token = get_csrf_token(session).await; |
| 28 |
|
| 29 |
match step { |
| 30 |
"type" => { |
| 31 |
let type_cards = ProjectFeature::wizard_type_cards(&project.features); |
| 32 |
|
| 33 |
if type_cards.len() <= 1 { |
| 34 |
let nav = build_step_nav(ITEM_STEPS, ITEM_LABELS, "basics"); |
| 35 |
return Ok(WizardItemBasicsTemplate { |
| 36 |
nav, |
| 37 |
project_slug, |
| 38 |
item_id, |
| 39 |
title: item.title.clone(), |
| 40 |
description: item.description.clone().unwrap_or_default(), |
| 41 |
cover_image_url: item.cover_image_url.clone(), |
| 42 |
} |
| 43 |
.into_response()); |
| 44 |
} |
| 45 |
Ok(WizardItemTypeTemplate { |
| 46 |
nav, |
| 47 |
project_slug, |
| 48 |
item_id, |
| 49 |
item_type_cards: type_cards, |
| 50 |
selected_type: item.item_type.to_string(), |
| 51 |
} |
| 52 |
.into_response()) |
| 53 |
} |
| 54 |
|
| 55 |
"basics" => Ok(WizardItemBasicsTemplate { |
| 56 |
nav, |
| 57 |
project_slug, |
| 58 |
item_id, |
| 59 |
title: item.title.clone(), |
| 60 |
description: item.description.clone().unwrap_or_default(), |
| 61 |
cover_image_url: item.cover_image_url.clone(), |
| 62 |
} |
| 63 |
.into_response()), |
| 64 |
|
| 65 |
"content" => { |
| 66 |
let content_template = item.item_type.to_string(); |
| 67 |
|
| 68 |
|
| 69 |
let (bundleable_items, selected_bundle_ids, unlisted_ids) = |
| 70 |
if item.item_type == ItemType::Bundle { |
| 71 |
let bundleable = |
| 72 |
db::bundles::get_bundleable_items(&state.db, project.id, Some(item.id)) |
| 73 |
.await?; |
| 74 |
let current = db::bundles::get_bundle_items(&state.db, item.id).await?; |
| 75 |
let selected: Vec<String> = |
| 76 |
current.iter().map(|i| i.id.to_string()).collect(); |
| 77 |
let unlisted: Vec<String> = current |
| 78 |
.iter() |
| 79 |
.chain(bundleable.iter()) |
| 80 |
.filter(|i| !i.listed) |
| 81 |
.map(|i| i.id.to_string()) |
| 82 |
.collect(); |
| 83 |
let bi: Vec<BundleableItem> = bundleable |
| 84 |
.iter() |
| 85 |
.map(|i| BundleableItem { |
| 86 |
id: i.id.to_string(), |
| 87 |
title: i.title.clone(), |
| 88 |
item_type: i.item_type.label().to_string(), |
| 89 |
}) |
| 90 |
.collect(); |
| 91 |
(bi, selected, unlisted) |
| 92 |
} else { |
| 93 |
(vec![], vec![], vec![]) |
| 94 |
}; |
| 95 |
|
| 96 |
Ok(WizardItemContentTemplate { |
| 97 |
nav, |
| 98 |
project_slug, |
| 99 |
project_id: project.id.to_string(), |
| 100 |
item_id, |
| 101 |
item_type: content_template, |
| 102 |
body: item.body.clone().unwrap_or_default(), |
| 103 |
bundleable_items, |
| 104 |
selected_bundle_ids, |
| 105 |
unlisted_ids, |
| 106 |
} |
| 107 |
.into_response()) |
| 108 |
} |
| 109 |
|
| 110 |
"sections" => { |
| 111 |
let db_sections = db::item_sections::list_by_item(&state.db, item.id).await?; |
| 112 |
let sections: Vec<crate::types::ItemSection> = |
| 113 |
db_sections.iter().map(crate::types::ItemSection::from).collect(); |
| 114 |
Ok(WizardItemSectionsTemplate { |
| 115 |
nav, |
| 116 |
project_slug, |
| 117 |
item_id, |
| 118 |
sections, |
| 119 |
} |
| 120 |
.into_response()) |
| 121 |
} |
| 122 |
|
| 123 |
"pricing" => { |
| 124 |
let pricing_model = if item.pwyw_enabled { |
| 125 |
"pwyw" |
| 126 |
} else if item.price_cents > 0 { |
| 127 |
"fixed" |
| 128 |
} else { |
| 129 |
"free" |
| 130 |
}; |
| 131 |
|
| 132 |
Ok(WizardItemPricingTemplate { |
| 133 |
nav, |
| 134 |
project_slug, |
| 135 |
item_id, |
| 136 |
pricing_model: pricing_model.to_string(), |
| 137 |
price_dollars: format!( |
| 138 |
"{}.{:02}", |
| 139 |
item.price_cents / 100, |
| 140 |
item.price_cents % 100 |
| 141 |
), |
| 142 |
pwyw_suggested_dollars: format!( |
| 143 |
"{}.{:02}", |
| 144 |
item.price_cents / 100, |
| 145 |
item.price_cents % 100 |
| 146 |
), |
| 147 |
pwyw_min_dollars: { |
| 148 |
let min = item.pwyw_min_cents.unwrap_or(0); |
| 149 |
format!("{}.{:02}", min / 100, min % 100) |
| 150 |
}, |
| 151 |
} |
| 152 |
.into_response()) |
| 153 |
} |
| 154 |
|
| 155 |
"preview" => { |
| 156 |
let tags = db::tags::get_tags_for_item(&state.db, item.id).await?; |
| 157 |
let tag_names: Vec<String> = tags.iter().map(|t| t.tag_name.clone()).collect(); |
| 158 |
|
| 159 |
Ok(WizardItemPreviewTemplate { |
| 160 |
csrf_token, |
| 161 |
nav, |
| 162 |
project_slug, |
| 163 |
item_id, |
| 164 |
title: item.title.clone(), |
| 165 |
item_type: item.item_type.to_string(), |
| 166 |
description: item.description.clone().unwrap_or_default(), |
| 167 |
price_display: format_price_display(item.price_cents, item.pwyw_enabled, item.pwyw_min_cents), |
| 168 |
tag_names, |
| 169 |
has_content: item.body.is_some() |
| 170 |
|| item.audio_s3_key.is_some() |
| 171 |
|| item.video_s3_key.is_some() |
| 172 |
|| (item.item_type == ItemType::Bundle |
| 173 |
&& db::bundles::get_bundle_item_count(&state.db, item.id).await? > 0), |
| 174 |
is_public: item.is_public, |
| 175 |
} |
| 176 |
.into_response()) |
| 177 |
} |
| 178 |
|
| 179 |
_ => Err(AppError::NotFound), |
| 180 |
} |
| 181 |
} |
| 182 |
|