From cca0f79ee64f576239dc44aec49a4fbe15c0f77a Mon Sep 17 00:00:00 2001 From: maebeale Date: Sat, 7 Mar 2026 21:27:02 -0500 Subject: [PATCH] Persist event registration slugs in session for public users Public (non-authenticated) users who register for events lose their ?reg= query param when navigating away from the event show page, causing the "View Registration" button to disappear. This stores registration slugs in the Rails session so the button persists across navigation to the events index and back. - Add EventRegistrationSession concern for session slug management - Store slugs on registration create, show, reactivate; clear on cancel - Fall back to session in _registration_section and _card partials - Pass ?reg= on "Back to Events" navigation links as belt-and-suspenders - Add request spec coverage for session storage/clearing Co-Authored-By: Claude Opus 4.6 --- .../concerns/event_registration_session.rb | 19 +++++++++ .../events/public_registrations_controller.rb | 2 + .../events/registrations_controller.rb | 6 +++ app/controllers/events_controller.rb | 10 ++++- app/views/events/_card.html.erb | 10 +++++ .../events/_registration_section.html.erb | 3 +- app/views/events/show.html.erb | 5 ++- spec/requests/events/registrations_spec.rb | 39 +++++++++++++++++++ spec/requests/events_spec.rb | 21 ++++++++++ 9 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 app/controllers/concerns/event_registration_session.rb diff --git a/app/controllers/concerns/event_registration_session.rb b/app/controllers/concerns/event_registration_session.rb new file mode 100644 index 000000000..833633f34 --- /dev/null +++ b/app/controllers/concerns/event_registration_session.rb @@ -0,0 +1,19 @@ +module EventRegistrationSession + extend ActiveSupport::Concern + + private + + def store_event_reg_slug(event_id, slug) + session[:event_reg_slugs] ||= {} + session[:event_reg_slugs][event_id.to_s] = slug + end + + def clear_event_reg_slug(event_id) + return unless session[:event_reg_slugs] + session[:event_reg_slugs].delete(event_id.to_s) + end + + def event_reg_slug_for(event_id) + session.dig(:event_reg_slugs, event_id.to_s) + end +end diff --git a/app/controllers/events/public_registrations_controller.rb b/app/controllers/events/public_registrations_controller.rb index a8a70284f..b32f5fe1e 100644 --- a/app/controllers/events/public_registrations_controller.rb +++ b/app/controllers/events/public_registrations_controller.rb @@ -1,5 +1,6 @@ module Events class PublicRegistrationsController < ApplicationController + include EventRegistrationSession skip_before_action :authenticate_user!, only: [ :new, :create, :show ] before_action :set_event before_action :ensure_registerable, only: [ :new, :create ] @@ -53,6 +54,7 @@ def create ) if result.success? + store_event_reg_slug(@event.id, result.event_registration.slug) redirect_to registration_ticket_path(result.event_registration.slug), notice: "You have been successfully registered!" else diff --git a/app/controllers/events/registrations_controller.rb b/app/controllers/events/registrations_controller.rb index 48c3e2f38..6954ca074 100644 --- a/app/controllers/events/registrations_controller.rb +++ b/app/controllers/events/registrations_controller.rb @@ -1,5 +1,6 @@ module Events class RegistrationsController < ApplicationController + include EventRegistrationSession before_action :authenticate_user!, only: [ :create, :destroy ] before_action :set_event, only: [ :create, :destroy ] before_action :set_registrant, only: [ :create, :destroy ] @@ -7,6 +8,7 @@ class RegistrationsController < ApplicationController def show authorize! @event_registration, to: :show_public? + store_event_reg_slug(@event_registration.event_id, @event_registration.slug) if @event_registration.active? end def resend_confirmation @@ -20,6 +22,7 @@ def cancel if @event_registration.active? @event_registration.update!(status: "cancelled") + clear_event_reg_slug(@event_registration.event_id) redirect_to registration_ticket_path(@event_registration.slug), notice: "Your registration has been cancelled." else redirect_to registration_ticket_path(@event_registration.slug), alert: "Registration is already cancelled." @@ -31,6 +34,7 @@ def reactivate if @event_registration.status == "cancelled" @event_registration.update!(status: "registered") + store_event_reg_slug(@event_registration.event_id, @event_registration.slug) redirect_to registration_ticket_path(@event_registration.slug), notice: "Your registration has been reactivated." else redirect_to registration_ticket_path(@event_registration.slug), alert: "Registration is not cancelled." @@ -44,6 +48,7 @@ def create authorize! existing existing.update!(status: "registered") send_registration_notifications(existing) + store_event_reg_slug(@event.id, existing.slug) success = "Your registration has been reactivated." respond_to do |format| format.turbo_stream { flash.now[:notice] = success } @@ -57,6 +62,7 @@ def create if @event_registration.save send_registration_notifications(@event_registration) + store_event_reg_slug(@event.id, @event_registration.slug) success = "You have successfully registered for this event." respond_to do |format| format.turbo_stream { flash.now[:notice] = success } diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index df771df8f..b64585df0 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -1,5 +1,5 @@ class EventsController < ApplicationController - include AhoyTracking, TagAssignable + include AhoyTracking, TagAssignable, EventRegistrationSession skip_before_action :authenticate_user!, only: [ :index, :show ] skip_before_action :verify_authenticity_token, only: [ :preview ] before_action :set_event, only: %i[ show edit update destroy preview manage preview_reminder send_reminder copy_registration_form ] @@ -8,12 +8,14 @@ def index authorize! base_scope = authorized_scope(Event.all) @events = base_scope.search_by_params(params).order(start_date: :desc) + persist_reg_slug_from_params end def show authorize! @event @event = @event.decorate track_view(@event) + persist_reg_slug_from_params end def new @@ -265,6 +267,12 @@ def set_event @event = Event.find(params[:id]) end + def persist_reg_slug_from_params + return unless params[:reg].present? + reg = EventRegistration.find_by(slug: params[:reg]) + store_event_reg_slug(reg.event_id, reg.slug) if reg&.active? + end + def event_params params.require(:event).permit(:cost, :created_by_id, diff --git a/app/views/events/_card.html.erb b/app/views/events/_card.html.erb index b0edbe501..1fa38a245 100644 --- a/app/views/events/_card.html.erb +++ b/app/views/events/_card.html.erb @@ -34,6 +34,11 @@ <% registered = current_user && event.actively_registered?(current_user.person) %> + <% unless registered + reg_slug = session.dig(:event_reg_slugs, event.id.to_s) + slug_registration = reg_slug ? EventRegistration.find_by(slug: reg_slug, event_id: event.id) : nil + slug_registered = slug_registration&.active? + end %>
<% if event.location.present? %> @@ -64,6 +69,11 @@ class: "btn px-3 py-2 text-xs uppercase leading-tight text-white hover:bg-white event-view-registration-btn", style: "font-family: 'Telefon Bold', sans-serif; background-color: rgb(22, 101, 52); border: 2px solid rgb(22, 101, 52);" %> <% end %> + <% elsif slug_registered %> + <%= link_to "View registration", registration_ticket_path(slug_registration.slug), + data: { turbo_frame: "_top" }, + class: "btn px-3 py-2 text-xs uppercase leading-tight text-white hover:bg-white event-view-registration-btn", + style: "font-family: 'Telefon Bold', sans-serif; background-color: rgb(22, 101, 52); border: 2px solid rgb(22, 101, 52);" %> <% elsif event.ended? %> Event ended <% if allowed_to?(:manage?, event) %> diff --git a/app/views/events/_registration_section.html.erb b/app/views/events/_registration_section.html.erb index 90dbb78f1..c30a7403a 100644 --- a/app/views/events/_registration_section.html.erb +++ b/app/views/events/_registration_section.html.erb @@ -1,7 +1,8 @@ <% instance ||= 1 %> <% button_text ||= "Register" %> <% registered = event.actively_registered?(current_user&.person) %> -<% slug_registration = params[:reg].present? ? EventRegistration.find_by(slug: params[:reg], event_id: event.id) : nil %> +<% reg_slug = params[:reg].presence || session.dig(:event_reg_slugs, event.id.to_s) %> +<% slug_registration = reg_slug ? EventRegistration.find_by(slug: reg_slug, event_id: event.id) : nil %> <% slug_registered = slug_registration&.active? %> <% slug_cancelled = slug_registration.present? && slug_registration.status == "cancelled" %> <%= tag.div id: dom_id(event.object, "registration_section_#{instance}"), class: "registration-section flex flex-col items-center gap-4 mb-6" do %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index d4e32f402..d64f86e53 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -8,10 +8,11 @@ <% else %>
- <%= link_to "← Back to Events", events_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> + <% reg_slug = params[:reg].presence || session.dig(:event_reg_slugs, @event.id.to_s) %> + <%= link_to "← Back to Events", events_path(reg: reg_slug), class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %>
<%= link_to "Home", root_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> - <%= link_to "Events", events_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> + <%= link_to "Events", events_path(reg: reg_slug), class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> <% if allowed_to?(:manage?, @event) && @event.object.event_forms.registration.exists? %> <%= link_to "Register (as visitor)", new_event_public_registration_path(@event), class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1 admin-only bg-blue-100" %> <% end %> diff --git a/spec/requests/events/registrations_spec.rb b/spec/requests/events/registrations_spec.rb index ddd82c312..a751307db 100644 --- a/spec/requests/events/registrations_spec.rb +++ b/spec/requests/events/registrations_spec.rb @@ -18,6 +18,20 @@ get registration_ticket_path(registration.slug) expect(response).to have_http_status(:success) end + + it "stores the slug in session for active registrations" do + get registration_ticket_path(registration.slug) + expect(session[:event_reg_slugs]).to eq({ event.id.to_s => registration.slug }) + end + end + + context "when the registration is cancelled" do + before { registration.update!(status: "cancelled") } + + it "does not store the slug in session" do + get registration_ticket_path(registration.slug) + expect(session[:event_reg_slugs]).to be_nil + end end context "as an admin" do @@ -94,6 +108,15 @@ expect(flash[:notice]).to eq("Your registration has been cancelled.") end + it "clears the slug from session" do + # First store the slug via show + get registration_ticket_path(registration.slug) + expect(session[:event_reg_slugs][event.id.to_s]).to eq(registration.slug) + + post registration_cancel_path(registration.slug) + expect(session[:event_reg_slugs][event.id.to_s]).to be_nil + end + it "does not cancel an already cancelled registration" do registration.update!(status: "cancelled") @@ -126,6 +149,11 @@ expect(flash[:notice]).to eq("Your registration has been reactivated.") end + it "stores the slug in session after reactivation" do + post registration_reactivate_path(registration.slug) + expect(session[:event_reg_slugs]).to eq({ event.id.to_s => registration.slug }) + end + it "does not reactivate an already active registration" do registration.update!(status: "registered") @@ -207,6 +235,12 @@ %w[event_registration_confirmation event_registration_confirmation_fyi] ) end + + it "stores the slug in session" do + post event_registrant_registration_path(event_id: event.id) + reg = EventRegistration.last + expect(session[:event_reg_slugs]).to eq({ event.id.to_s => reg.slug }) + end end context "when a cancelled registration exists" do @@ -243,6 +277,11 @@ expect(response).to redirect_to(registration_ticket_path(cancelled_registration.slug)) expect(flash[:notice]).to eq("Your registration has been reactivated.") end + + it "stores the slug in session on reactivation" do + post event_registrant_registration_path(event_id: event.id) + expect(session[:event_reg_slugs]).to eq({ event.id.to_s => cancelled_registration.slug }) + end end context "when creation fails" do diff --git a/spec/requests/events_spec.rb b/spec/requests/events_spec.rb index cbd79016a..ee77fbe80 100644 --- a/spec/requests/events_spec.rb +++ b/spec/requests/events_spec.rb @@ -29,6 +29,13 @@ expect(response).to have_http_status(:ok) end + it "stores reg slug in session when reg param is present" do + sign_in user + registration = create(:event_registration, event: event, registrant: create(:person)) + get events_path(reg: registration.slug) + expect(session[:event_reg_slugs]).to eq({ event.id.to_s => registration.slug }) + end + context "when user time_zone is set" do # 19:00 UTC = 12:00 noon PT = 15:00 (3 pm) ET (June 15, 2031 with DST) let(:utc_start) { Time.utc(2031, 6, 15, 19, 0, 0) } @@ -64,6 +71,20 @@ end describe "GET /show" do + it "stores reg slug in session when reg param is present" do + sign_in user + registration = create(:event_registration, event: event, registrant: create(:person)) + get event_path(event, reg: registration.slug) + expect(session[:event_reg_slugs]).to eq({ event.id.to_s => registration.slug }) + end + + it "does not store slug for cancelled registrations" do + sign_in user + registration = create(:event_registration, event: event, registrant: create(:person), status: "cancelled") + get event_path(event, reg: registration.slug) + expect(session[:event_reg_slugs]).to be_nil + end + context "when event has ended" do let(:ended_event) { create(:event, :published, :ended) }