Skip to main content

max / makenotwork

3.6 KB · 107 lines History Blame Raw
1 //! Creator waitlist application.
2
3 use axum::{
4 extract::State,
5 http::{header::HeaderMap, StatusCode},
6 response::{IntoResponse, Response},
7 Form,
8 };
9 use serde::Deserialize;
10
11 use crate::{
12 auth::AuthUser,
13 db,
14 error::{AppError, Result},
15 helpers::is_htmx_request,
16 templates::AlertTemplate,
17 validation,
18 AppState,
19 };
20
21 /// Form input for applying to the creator waitlist.
22 #[derive(Debug, Deserialize)]
23 pub struct WaitlistApplyForm {
24 pub pitch: String,
25 /// Preferred creator tier (basic, small_files, big_files, everything).
26 #[serde(default)]
27 pub preferred_tier: Option<String>,
28 /// Whether the applicant is requesting a free trial.
29 #[serde(default)]
30 pub free_trial: Option<String>,
31 /// Requested trial length (e.g. "2 weeks", "1 month").
32 #[serde(default)]
33 pub trial_length: Option<String>,
34 /// What the applicant wants to test during the trial.
35 #[serde(default)]
36 pub trial_reason: Option<String>,
37 }
38
39 /// Submit an application to the creator waitlist.
40 #[tracing::instrument(skip_all, name = "users::waitlist_apply")]
41 pub(in crate::routes::api) async fn waitlist_apply(
42 State(state): State<AppState>,
43 headers: HeaderMap,
44 AuthUser(user): AuthUser,
45 Form(form): Form<WaitlistApplyForm>,
46 ) -> Result<Response> {
47 let is_htmx = is_htmx_request(&headers);
48
49 // Check if user already has creator access
50 if user.can_create_projects {
51 if is_htmx {
52 return Ok(AlertTemplate::new("info", "You already have creator access.").into_response());
53 }
54 return Err(AppError::BadRequest("Already a creator".to_string()));
55 }
56
57 // Check verified email
58 let db_user = db::users::get_user_by_id(&state.db, user.id)
59 .await?
60 .ok_or(AppError::NotFound)?;
61
62 if !db_user.email_verified {
63 if is_htmx {
64 return Ok(AlertTemplate::new("error", "Please verify your email first.").into_response());
65 }
66 return Err(AppError::BadRequest("Email not verified".to_string()));
67 }
68
69 // Check if already applied
70 if db::waitlist::get_waitlist_entry_by_user(&state.db, user.id).await?.is_some() {
71 if is_htmx {
72 return Ok(AlertTemplate::new("info", "You've already applied.").into_response());
73 }
74 return Err(AppError::BadRequest("Already applied".to_string()));
75 }
76
77 // Validate pitch
78 let pitch = form.pitch.trim().to_string();
79 validation::validate_waitlist_pitch(&pitch)?;
80
81 // Build the full application text with optional fields appended.
82 let mut full_pitch = pitch;
83 if let Some(tier) = form.preferred_tier.as_deref().filter(|s| !s.is_empty()) {
84 full_pitch.push_str(&format!("\n\n[Preferred tier: {}]", tier));
85 }
86 if form.free_trial.as_deref() == Some("yes") {
87 let length = form.trial_length.as_deref().unwrap_or("not specified").trim();
88 let reason = form.trial_reason.as_deref().unwrap_or("").trim();
89 full_pitch.push_str(&format!("\n\n[Free trial requested: {}]", length));
90 if !reason.is_empty() {
91 full_pitch.push_str(&format!("\n[Trial reason: {}]", reason));
92 }
93 }
94
95 // Create entry
96 db::waitlist::create_waitlist_entry(&state.db, user.id, &full_pitch).await?;
97
98 tracing::info!(user_id = %user.id, "waitlist application submitted");
99
100 if is_htmx {
101 // Return a success message that replaces the form
102 return Ok(AlertTemplate::new("success", "Application submitted. We'll review it and let you know.").into_response());
103 }
104
105 Ok(StatusCode::NO_CONTENT.into_response())
106 }
107