Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/controllers/events/public_registrations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def create
checkout_session = create_stripe_checkout_session(registration, result.form_submission)
redirect_to checkout_session.url, allow_other_host: true, status: :see_other
else
redirect_to registration_ticket_path(registration.slug),
redirect_to registration_ticket_path(registration.slug, reg: params[:reg].presence),
notice: "You have been successfully registered!"
end
else
Expand Down Expand Up @@ -165,8 +165,8 @@ def create_stripe_checkout_session(registration, submission = nil)
},
quantity: 1
} ],
success_url: registration_ticket_url(registration.slug, checkout: "success"),
cancel_url: registration_ticket_url(registration.slug, checkout: "cancelled")
success_url: registration_ticket_url(registration.slug, checkout: "success", reg: params[:reg].presence),
cancel_url: registration_ticket_url(registration.slug, checkout: "cancelled", reg: params[:reg].presence)
)

registration.update!(checkout_session_id: checkout_session.id)
Expand Down
27 changes: 27 additions & 0 deletions app/helpers/events_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
module EventsHelper
# Separates registration slugs packed into a single `?reg=` param so public
# (logged-out) users keep the "View registration" button lit across several
# events in one session. A period is used because it never appears in a
# urlsafe_base64 slug and stays unescaped in a URL, keeping it human-readable.
REG_PARAM_DELIMITER = "."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Period is the delimiter because slugs are urlsafe_base64 (alphabet includes - and _) — a hyphen delimiter could collide with a slug that contains one. . never appears in base64url and stays unescaped in URLs.


# The registration slugs carried in the current request's `?reg=` param.
def reg_param_slugs
params[:reg].to_s.split(REG_PARAM_DELIMITER)
end

# The registration for this event that the visitor has "unlocked" via their
# `?reg=` param, if any. Slugs are the authorization token, so no ownership
# check is needed. Returns nil when none of the param slugs belong to the event.
def reg_param_registration(event)
slugs = reg_param_slugs
return if slugs.empty?
EventRegistration.where(slug: slugs, event_id: event.id).first
end

# The current `?reg=` slug set with `slug` merged in, for forwarding onward
# links after a registration so the new event's button stays lit alongside
# any events registered earlier this session.
def reg_param_with(slug)
(reg_param_slugs << slug).uniq.join(REG_PARAM_DELIMITER)
end

# Stable anchor id for a registrant's row on the Onboarding matrix, so back-links
# from detail pages can scroll to (and highlight) the row they came from.
def onboarding_row_id(record_or_id)
Expand Down
13 changes: 11 additions & 2 deletions app/views/events/_card.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

<!-- Title + badges -->
<div class="flex flex-col gap-2 flex-1 min-w-0 pr-1 mr-4 mt-1">
<%= link_to event_path(event),
<%= link_to event_path(event, reg: params[:reg].presence),
class: "hover:underline block leading-tight",
data: { turbo_prefetch: false, turbo: false } do %>
<%= title_with_badges(event).html_safe %>
Expand All @@ -34,6 +34,10 @@

<!-- Location + Times + Buttons (bottom-aligned group) -->
<% registered = current_user && event.actively_registered?(current_user.person) %>
<% unless registered
slug_registration = reg_param_registration(event)
slug_registered = slug_registration&.active?
end %>
<div class="mt-auto flex flex-col gap-4">
<div class="text-sm text-gray-700 leading-none">
<% if event.location.present? %>
Expand All @@ -58,6 +62,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? %>
<span class="text-sm text-gray-500 italic">Event ended</span>
<% if allowed_to?(:manage?, event) %>
Expand All @@ -82,7 +91,7 @@
<% end %>
<% elsif event.object.public_registration_enabled? && event.object.event_forms.registration.exists? %>
<%= link_to "Register",
new_event_public_registration_path(event),
new_event_public_registration_path(event, reg: params[:reg].presence),
data: { turbo_frame: "_top" },
class: "btn px-3 py-2 text-xs uppercase leading-tight font-telefon text-accent border-2 border-accent hover:text-white hover:bg-orange-700" %>
<% end %>
Expand Down
6 changes: 3 additions & 3 deletions app/views/events/_registration_section.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<% 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 %>
<% slug_registration = reg_param_registration(event) %>
<% 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 %>
Expand Down Expand Up @@ -29,13 +29,13 @@
style: "font-family: 'Telefon Bold', sans-serif;" %>
<% else %>
<%= link_to button_text,
new_event_public_registration_path(event),
new_event_public_registration_path(event, reg: params[:reg].presence),
class: "btn btn-accent px-10 py-2 text-2xl uppercase",
style: "font-family: 'Telefon Bold', sans-serif;" %>
<% end %>
<% elsif event.object.public_registration_enabled? && event.object.event_forms.registration.exists? %>
<%= link_to button_text,
new_event_public_registration_path(event),
new_event_public_registration_path(event, reg: params[:reg].presence),
class: "btn btn-accent px-10 py-2 text-2xl uppercase",
style: "font-family: 'Telefon Bold', sans-serif;" %>
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/events/public_registrations/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
</div>
<% end %>

<%= form_with url: event_public_registration_path(@event), method: :post, local: true, class: "space-y-2", data: { turbo: !@event.cost_cents.to_i.positive? } do |f| %>
<%= form_with url: event_public_registration_path(@event, reg: params[:reg].presence), method: :post, local: true, class: "space-y-2", data: { turbo: !@event.cost_cents.to_i.positive? } do |f| %>

<% if @scholarship %>
<input type="hidden" name="scholarship_requested" value="true">
Expand Down
2 changes: 1 addition & 1 deletion app/views/events/registrations/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<% content_for(:page_bg_class, "public") %>
<div class="max-w-3xl mx-auto flex flex-wrap items-center gap-2 px-4 pt-4">
<%= link_to event_path(@event_registration.event, reg: @event_registration.slug), class: "inline-flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-700 px-2 py-1" do %>
<%= link_to event_path(@event_registration.event, reg: reg_param_with(@event_registration.slug)), class: "inline-flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-700 px-2 py-1" do %>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: This is the one place a slug gets added to the set — after any registration the visitor lands here, so merging this ticket's slug into the inbound ?reg= param is what lets a second/third event stay lit alongside earlier ones.

<i class="fa-solid fa-arrow-left text-xs"></i> Back to event
<% end %>
<div class="ml-auto flex gap-2">
Expand Down
2 changes: 1 addition & 1 deletion app/views/events/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<% else %>
<!-- Top Right Actions -->
<div class="flex flex-wrap items-center gap-2 mb-4">
<%= 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: params[:reg].presence), class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %>
<div class="ml-auto flex flex-wrap gap-1">
<%= link_to "Home", root_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %>
<% if @event.event_registrations.active.exists? %>
Expand Down
6 changes: 6 additions & 0 deletions spec/requests/events/registrations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
get registration_ticket_path(registration.slug)
expect(response).to have_http_status(:success)
end

it "merges this slug into the reg param on the back-to-event link" do
prior = create(:event_registration, registrant: create(:person))
get registration_ticket_path(registration.slug, reg: prior.slug)
expect(response.body).to include("reg=#{prior.slug}.#{registration.slug}")
end
end

context "as an admin" do
Expand Down
43 changes: 43 additions & 0 deletions spec/requests/events_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,33 @@ def offer_ce!(target_event)
expect(response).to have_http_status(:ok)
end

it "shows a View registration link on the card when an active reg param is present" do
published_event = create(:event, :published)
sign_in user
registration = create(:event_registration, event: published_event, registrant: create(:person))
get events_path(reg: registration.slug)
expect(response.body).to include("href=\"#{registration_ticket_path(registration.slug)}\"")
end

it "does not show a View registration link for a cancelled reg param" do
published_event = create(:event, :published)
sign_in user
registration = create(:event_registration, event: published_event, registrant: create(:person), status: "cancelled")
get events_path(reg: registration.slug)
expect(response.body).not_to include("href=\"#{registration_ticket_path(registration.slug)}\"")
end

it "shows View registration links for several events packed into one reg param" do
event_a = create(:event, :published)
event_b = create(:event, :published)
sign_in user
reg_a = create(:event_registration, event: event_a, registrant: create(:person))
reg_b = create(:event_registration, event: event_b, registrant: create(:person))
get events_path(reg: "#{reg_a.slug}.#{reg_b.slug}")
expect(response.body).to include("href=\"#{registration_ticket_path(reg_a.slug)}\"")
expect(response.body).to include("href=\"#{registration_ticket_path(reg_b.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) }
Expand Down Expand Up @@ -71,6 +98,22 @@ def offer_ce!(target_event)
end

describe "GET /show" do
it "shows a View registration link when an active reg param is present" do
published_event = create(:event, :published)
sign_in user
registration = create(:event_registration, event: published_event, registrant: create(:person))
get event_path(published_event, reg: registration.slug)
expect(response.body).to include("href=\"#{registration_ticket_path(registration.slug)}\"")
end

it "does not show a View registration link for a cancelled reg param" do
published_event = create(:event, :published)
sign_in user
registration = create(:event_registration, event: published_event, registrant: create(:person), status: "cancelled")
get event_path(published_event, reg: registration.slug)
expect(response.body).not_to include("href=\"#{registration_ticket_path(registration.slug)}\"")
end

context "when event has ended" do
let(:ended_event) { create(:event, :published, :ended) }

Expand Down