The smokesignal.events web application

feature: direct rsvp url behavior tuning

+90 -38
+8
src/http/handle_create_rsvp.rs
··· 439 439 build_rsvp_form.subject_aturi.clone().unwrap().as_str(), 440 440 )?; 441 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 + 442 449 return Ok(RenderHtml( 443 450 &render_template, 444 451 web_context.engine.clone(), 445 452 template_context! { ..default_context, ..template_context! { 446 453 build_rsvp_form, 447 454 event_url, 455 + redirect_url, 448 456 }}, 449 457 ) 450 458 .into_response());
+19 -1
src/http/handle_edit_settings.rs
··· 102 102 } 103 103 }; 104 104 105 - // Validate redirect URL when direct RSVP is disabled 105 + // Validate redirect URL settings 106 106 let disable_direct_rsvp = form.disable_direct_rsvp.unwrap_or(false); 107 107 let rsvp_redirect_url = form.rsvp_redirect_url.as_deref().map(|s| s.trim()); 108 108 109 + // When disable_direct_rsvp is true, URL is required 109 110 if disable_direct_rsvp { 110 111 if let Some(url) = rsvp_redirect_url { 111 112 if url.is_empty() || !url.starts_with("https://") { ··· 129 130 ); 130 131 } 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()); 132 150 133 151 // Update the event in the database only (settings are not stored in AT Protocol) 134 152 let event_update_result = event_update_with_full_metadata(
+21 -4
templates/en-us/create_rsvp.partial.html
··· 2 2 {% if hx_request %} 3 3 <article class="message is-success"> 4 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 %} 5 16 <p>The RSVP has been recorded!</p> 17 + {% endif %} 6 18 </div> 7 19 </article> 8 20 {% else %} 9 21 <article class="message is-success"> 10 22 <div class="message-header"> 11 - <p>The RSVP has been recorded!</p> 23 + <p>{% if redirect_url %}Your RSVP has been recorded!{% else %}The RSVP has been recorded!{% endif %}</p> 12 24 </div> 13 25 <div class="message-body"> 14 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 %} 15 34 <a class="button" href="{{ event_url }}"> 16 - <span class="icon"> 17 - <i class="fas fa-file"></i> 18 - </span> 35 + <span class="icon"><i class="fas fa-file"></i></span> 19 36 <span>View Event</span> 20 37 </a> 21 38 </p>
+42 -33
templates/en-us/manage_event_settings_tab.html
··· 41 41 42 42 <div class="content"> 43 43 <h3 class="subtitle is-6">External Ticketing</h3> 44 + 45 + {# Status display #} 44 46 <p class="mb-3"> 45 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 %} 46 54 <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> 55 + <span class="icon"><i class="fas fa-ticket-alt"></i></span> 56 + <span>Post-RSVP Redirect</span> 51 57 </span> 52 - <span class="ml-2">Users are redirected to external ticketing.</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 %} 53 67 <div class="mt-2"> 54 68 <strong>Redirect URL:</strong> 55 69 <a href="{{ event.rsvp_redirect_url }}" target="_blank" rel="noopener noreferrer"> ··· 57 71 <span class="icon is-small"><i class="fas fa-external-link-alt"></i></span> 58 72 </a> 59 73 </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 74 {% endif %} 69 75 </p> 70 76 71 77 {# Edit form for external ticketing settings #} 72 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 #} 73 95 <div class="field"> 74 96 <div class="control"> 75 97 <label class="checkbox"> 76 98 <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 99 + {% if event.disable_direct_rsvp %}checked{% endif %}> 100 + <strong>Disable direct RSVPs entirely</strong> 80 101 </label> 81 102 </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> 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> 96 107 </div> 97 108 98 109 <div class="field"> 99 110 <div class="control"> 100 111 <button type="submit" class="button is-primary"> 101 - <span class="icon"> 102 - <i class="fas fa-save"></i> 103 - </span> 112 + <span class="icon"><i class="fas fa-save"></i></span> 104 113 <span>Save Ticketing Settings</span> 105 114 </button> 106 115 </div>