From 151083332584a7c1c864fd77833e99da30f460b7 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 | 23 +++++++++++ 9 files changed, 113 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 ade7bfcec..96b738b77 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 a6b82c20c..d2c808a1c 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." @@ -43,6 +47,7 @@ def create if existing&.status == "cancelled" authorize! existing existing.update!(status: "registered") + 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 } @@ -55,6 +60,7 @@ def create authorize! @event_registration if @event_registration.save + 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 c043b3f52..cbb90e830 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 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 @@ -228,6 +230,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 5ba05ace0..5c2d72150 100644 --- a/app/views/events/_card.html.erb +++ b/app/views/events/_card.html.erb @@ -30,6 +30,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? %> @@ -55,6 +60,11 @@ 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 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 9238b97bf..e439898e5 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 3cf3d7304..30b44d351 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 @@ -91,6 +105,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") @@ -123,6 +146,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") @@ -193,6 +221,12 @@ expect(response.media_type).to eq("text/vnd.turbo-stream.html") expect(flash.now[:notice]).to eq("You have successfully registered for this event.") 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 @@ -218,6 +252,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 eac574b37..1309c4a9d 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, 2025 with DST) let(:utc_start) { Time.utc(2025, 6, 15, 19, 0, 0) } @@ -63,6 +70,22 @@ end 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 + end + describe "GET /new" do context "as admin" do it "renders successfully" do