The smokesignal.events web application

feature: direct rsvp url behavior tuning

+90 -38
+8
src/http/handle_create_rsvp.rs
··· 439 build_rsvp_form.subject_aturi.clone().unwrap().as_str(), 440 )?; 441 442 return Ok(RenderHtml( 443 &render_template, 444 web_context.engine.clone(), 445 template_context! { ..default_context, ..template_context! { 446 build_rsvp_form, 447 event_url, 448 }}, 449 ) 450 .into_response());
··· 439 build_rsvp_form.subject_aturi.clone().unwrap().as_str(), 440 )?; 441 442 + // Pass redirect URL only for "going" RSVPs 443 + let redirect_url = if is_going_rsvp { 444 + event.rsvp_redirect_url.clone() 445 + } else { 446 + None 447 + }; 448 + 449 return Ok(RenderHtml( 450 &render_template, 451 web_context.engine.clone(), 452 template_context! { ..default_context, ..template_context! { 453 build_rsvp_form, 454 event_url, 455 + redirect_url, 456 }}, 457 ) 458 .into_response());
+19 -1
src/http/handle_edit_settings.rs
··· 102 } 103 }; 104 105 - // Validate redirect URL when direct RSVP is disabled 106 let disable_direct_rsvp = form.disable_direct_rsvp.unwrap_or(false); 107 let rsvp_redirect_url = form.rsvp_redirect_url.as_deref().map(|s| s.trim()); 108 109 if disable_direct_rsvp { 110 if let Some(url) = rsvp_redirect_url { 111 if url.is_empty() || !url.starts_with("https://") { ··· 129 ); 130 } 131 } 132 133 // Update the event in the database only (settings are not stored in AT Protocol) 134 let event_update_result = event_update_with_full_metadata(
··· 102 } 103 }; 104 105 + // Validate redirect URL settings 106 let disable_direct_rsvp = form.disable_direct_rsvp.unwrap_or(false); 107 let rsvp_redirect_url = form.rsvp_redirect_url.as_deref().map(|s| s.trim()); 108 109 + // When disable_direct_rsvp is true, URL is required 110 if disable_direct_rsvp { 111 if let Some(url) = rsvp_redirect_url { 112 if url.is_empty() || !url.starts_with("https://") { ··· 130 ); 131 } 132 } 133 + 134 + // When URL is provided, validate it's HTTPS (even if disable_direct_rsvp=false) 135 + if let Some(url) = rsvp_redirect_url { 136 + if !url.is_empty() && !url.starts_with("https://") { 137 + return contextual_error!( 138 + ctx.web_context, 139 + ctx.language, 140 + error_template, 141 + default_context, 142 + CommonError::FieldRequired, 143 + StatusCode::BAD_REQUEST 144 + ); 145 + } 146 + } 147 + 148 + // Convert empty string to None for database storage 149 + let rsvp_redirect_url = rsvp_redirect_url.filter(|url| !url.is_empty()); 150 151 // Update the event in the database only (settings are not stored in AT Protocol) 152 let event_update_result = event_update_with_full_metadata(
+21 -4
templates/en-us/create_rsvp.partial.html
··· 2 {% if hx_request %} 3 <article class="message is-success"> 4 <div class="message-body"> 5 <p>The RSVP has been recorded!</p> 6 </div> 7 </article> 8 {% else %} 9 <article class="message is-success"> 10 <div class="message-header"> 11 - <p>The RSVP has been recorded!</p> 12 </div> 13 <div class="message-body"> 14 <p class="buttons"> 15 <a class="button" href="{{ event_url }}"> 16 - <span class="icon"> 17 - <i class="fas fa-file"></i> 18 - </span> 19 <span>View Event</span> 20 </a> 21 </p>
··· 2 {% if hx_request %} 3 <article class="message is-success"> 4 <div class="message-body"> 5 + {% if redirect_url %} 6 + <p>Your RSVP has been recorded!</p> 7 + <p class="mt-3"> 8 + <a href="{{ redirect_url }}" class="button is-primary" target="_blank" rel="noopener noreferrer"> 9 + <span class="icon"><i class="fas fa-ticket-alt"></i></span> 10 + <span>Continue to Tickets</span> 11 + <span class="icon is-small"><i class="fas fa-external-link-alt"></i></span> 12 + </a> 13 + </p> 14 + <p class="help mt-2">Click the button above to proceed to the external ticketing page.</p> 15 + {% else %} 16 <p>The RSVP has been recorded!</p> 17 + {% endif %} 18 </div> 19 </article> 20 {% else %} 21 <article class="message is-success"> 22 <div class="message-header"> 23 + <p>{% if redirect_url %}Your RSVP has been recorded!{% else %}The RSVP has been recorded!{% endif %}</p> 24 </div> 25 <div class="message-body"> 26 <p class="buttons"> 27 + {% if redirect_url %} 28 + <a class="button is-primary" href="{{ redirect_url }}" target="_blank" rel="noopener noreferrer"> 29 + <span class="icon"><i class="fas fa-ticket-alt"></i></span> 30 + <span>Continue to Tickets</span> 31 + <span class="icon is-small"><i class="fas fa-external-link-alt"></i></span> 32 + </a> 33 + {% endif %} 34 <a class="button" href="{{ event_url }}"> 35 + <span class="icon"><i class="fas fa-file"></i></span> 36 <span>View Event</span> 37 </a> 38 </p>
+42 -33
templates/en-us/manage_event_settings_tab.html
··· 41 42 <div class="content"> 43 <h3 class="subtitle is-6">External Ticketing</h3> 44 <p class="mb-3"> 45 {% if event.disable_direct_rsvp %} 46 <span class="tag is-info is-light"> 47 - <span class="icon"> 48 - <i class="fas fa-ticket-alt"></i> 49 - </span> 50 - <span>Enabled</span> 51 </span> 52 - <span class="ml-2">Users are redirected to external ticketing.</span> 53 <div class="mt-2"> 54 <strong>Redirect URL:</strong> 55 <a href="{{ event.rsvp_redirect_url }}" target="_blank" rel="noopener noreferrer"> ··· 57 <span class="icon is-small"><i class="fas fa-external-link-alt"></i></span> 58 </a> 59 </div> 60 - {% else %} 61 - <span class="tag is-light"> 62 - <span class="icon"> 63 - <i class="fas fa-times"></i> 64 - </span> 65 - <span>Disabled</span> 66 - </span> 67 - <span class="ml-2">Users can RSVP directly on Smoke Signal.</span> 68 {% endif %} 69 </p> 70 71 {# Edit form for external ticketing settings #} 72 <form hx-post="/{{ handle_slug }}/{{ event_rkey }}/edit-settings" hx-target="this" hx-swap="outerHTML"> 73 <div class="field"> 74 <div class="control"> 75 <label class="checkbox"> 76 <input type="checkbox" name="disable_direct_rsvp" value="true" 77 - {% if event.disable_direct_rsvp %}checked{% endif %} 78 - onchange="document.getElementById('rsvp-redirect-url-field').classList.toggle('is-hidden', !this.checked)"> 79 - Redirect RSVPs to external ticketing system 80 </label> 81 </div> 82 - <p class="help">When enabled, the "Going" button will redirect users to your external ticketing URL instead of creating an RSVP.</p> 83 - </div> 84 - 85 - <div class="field {% if not event.disable_direct_rsvp %}is-hidden{% endif %}" id="rsvp-redirect-url-field"> 86 - <label class="label">Ticket Purchase URL</label> 87 - <div class="control has-icons-left"> 88 - <input class="input" type="url" name="rsvp_redirect_url" 89 - value="{{ event.rsvp_redirect_url | default(value='') }}" 90 - placeholder="https://ti.to/your-event/tickets"> 91 - <span class="icon is-small is-left"> 92 - <i class="fas fa-link"></i> 93 - </span> 94 - </div> 95 - <p class="help">Users clicking "Going" will be redirected to this URL. Must be HTTPS.</p> 96 </div> 97 98 <div class="field"> 99 <div class="control"> 100 <button type="submit" class="button is-primary"> 101 - <span class="icon"> 102 - <i class="fas fa-save"></i> 103 - </span> 104 <span>Save Ticketing Settings</span> 105 </button> 106 </div>
··· 41 42 <div class="content"> 43 <h3 class="subtitle is-6">External Ticketing</h3> 44 + 45 + {# Status display #} 46 <p class="mb-3"> 47 {% if event.disable_direct_rsvp %} 48 + <span class="tag is-warning is-light"> 49 + <span class="icon"><i class="fas fa-ban"></i></span> 50 + <span>RSVPs Disabled</span> 51 + </span> 52 + <span class="ml-2">Users are redirected to external ticketing without recording an RSVP.</span> 53 + {% elif event.rsvp_redirect_url %} 54 <span class="tag is-info is-light"> 55 + <span class="icon"><i class="fas fa-ticket-alt"></i></span> 56 + <span>Post-RSVP Redirect</span> 57 </span> 58 + <span class="ml-2">Users RSVP on Smoke Signal, then are directed to external ticketing.</span> 59 + {% else %} 60 + <span class="tag is-light"> 61 + <span class="icon"><i class="fas fa-check"></i></span> 62 + <span>Standard RSVPs</span> 63 + </span> 64 + <span class="ml-2">Users can RSVP directly on Smoke Signal.</span> 65 + {% endif %} 66 + {% if event.rsvp_redirect_url %} 67 <div class="mt-2"> 68 <strong>Redirect URL:</strong> 69 <a href="{{ event.rsvp_redirect_url }}" target="_blank" rel="noopener noreferrer"> ··· 71 <span class="icon is-small"><i class="fas fa-external-link-alt"></i></span> 72 </a> 73 </div> 74 {% endif %} 75 </p> 76 77 {# Edit form for external ticketing settings #} 78 <form hx-post="/{{ handle_slug }}/{{ event_rkey }}/edit-settings" hx-target="this" hx-swap="outerHTML"> 79 + {# URL field - always visible #} 80 + <div class="field"> 81 + <label class="label">Ticket Purchase URL (Optional)</label> 82 + <div class="control has-icons-left"> 83 + <input class="input" type="url" name="rsvp_redirect_url" 84 + value="{{ event.rsvp_redirect_url | default(value='') }}" 85 + placeholder="https://ti.to/your-event/tickets"> 86 + <span class="icon is-small is-left"><i class="fas fa-link"></i></span> 87 + </div> 88 + <p class="help"> 89 + When set, users who RSVP as "Going" will see a button to continue to this URL. 90 + Must be HTTPS. Leave empty for standard RSVPs. 91 + </p> 92 + </div> 93 + 94 + {# Disable direct RSVP checkbox #} 95 <div class="field"> 96 <div class="control"> 97 <label class="checkbox"> 98 <input type="checkbox" name="disable_direct_rsvp" value="true" 99 + {% if event.disable_direct_rsvp %}checked{% endif %}> 100 + <strong>Disable direct RSVPs entirely</strong> 101 </label> 102 </div> 103 + <p class="help"> 104 + When enabled, users will ONLY see a "Get Tickets" button linking to the URL above. 105 + No RSVP will be recorded on Smoke Signal. Requires the redirect URL to be set. 106 + </p> 107 </div> 108 109 <div class="field"> 110 <div class="control"> 111 <button type="submit" class="button is-primary"> 112 + <span class="icon"><i class="fas fa-save"></i></span> 113 <span>Save Ticketing Settings</span> 114 </button> 115 </div>