Skip to content
Open
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ end
- `print_options` — Print options toggle for analytics
- `reminder_preview` — Live-preview the reminder email as the admin composes it on the bulk-reminder page: custom message, subject, and the event-details box toggle (announced to screen readers)
- `remote_select` — AJAX-powered select dropdown
- `reveal_section` — Expand a collapsible section and scroll to it when loaded via matching URL hash
- `reveal_section` — Expand a collapsible section and scroll to it when loaded via matching URL hash or `scroll_to` query parameter
- `rhino_source` — Rich text editor integration
- `scholarship_preview` — Live-preview the scholarship's allocated amount as the edit form changes
- `scroll_to_top` — Scrolls the window to the top on connect (used by the duplicate-person warning so it comes into view on create)
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ def feature_recipient_shoutout
# accurate "locked" warning instead of the generic prompt. (#1944)
locked = @event.event_registrations.find_by(id: params[:registration_id])&.editing_locked?
message = locked ? "That registrant was transferred out — their registration is locked. Undo the transfer to feature them." : "Choose a recipient to feature."
redirect_to recipients_event_path(@event), alert: message and return
redirect_to recipients_event_path(@event), alert: message, status: :see_other and return
end

authorize! registration, to: :update?
Expand All @@ -401,7 +401,7 @@ def feature_recipient_shoutout
if registrant.shoutout_text.blank? && (bio = shoutout_bio_from_profile(registrant))
registrant.update!(shoutout_text: bio)
end
redirect_to edit_event_registration_path(registration, return_to: "recipients", anchor: "shout-out"),
redirect_to edit_event_registration_path(registration, return_to: "recipients", scroll_to: "shout-out"),
notice: "Featured on the recipients page — review their shout-out text below.", status: :see_other
end

Expand Down
20 changes: 11 additions & 9 deletions app/frontend/javascript/controllers/reveal_section_controller.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Controller } from "@hotwired/stimulus"

// Expands a collapsible section and scrolls it into view when the page loads
// with a matching URL hash — e.g. when linked from another page so the user
// lands on the section already open rather than collapsed or scrolled past.
// with a matching URL hash or scroll_to query parameter. The query parameter
// carries the target through Turbo form redirects, which drop URL fragments.
//
// <div data-controller="dropdown reveal-section"
// data-reveal-section-anchor-value="registration_form_section"
Expand All @@ -19,16 +19,18 @@ export default class extends Controller {
static values = { anchor: String }

connect() {
if (window.location.hash.slice(1) !== this.anchorValue) return
const url = new URL(window.location.href)
const matchingHash = url.hash.slice(1) === this.anchorValue
const matchingQuery = url.searchParams.get("scroll_to") === this.anchorValue
if (!matchingHash && !matchingQuery) return

if (this.hasContentTarget && this.contentTarget.classList.contains("hidden")) {
this.toggleTarget.click()
}
this.element.scrollIntoView({ behavior: "smooth", block: "start" })
// Drop the hash once it has been honoured. A section inside a lazily-loaded
// frame reconnects on every frame render (each filter change), and a Turbo
// frame submit never touches the address bar — so the hash would still match
// and pull the page back here for the rest of the visit.
const { pathname, search } = window.location
history.replaceState(history.state, "", pathname + search)

if (matchingQuery) url.searchParams.delete("scroll_to")
url.hash = ""
history.replaceState(history.state, "", `${url.pathname}${url.search}`)
}
}
3 changes: 2 additions & 1 deletion app/views/event_registrations/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,8 @@
registrant's profile (Person#shoutout_text); editing it here updates
their profile via nested attributes. ---- %>
<%= locked_fieldset(locked) do %>
<section id="shout-out" class="scroll-mt-24 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm">
<section id="shout-out" class="scroll-mt-24 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm"
data-controller="reveal-section" data-reveal-section-anchor-value="shout-out">
<div class="flex items-center gap-3 border-b border-gray-100 px-4 py-3">
<span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg <%= section_icon_class(:scholarships, f.object.shoutout? || f.object.registrant.shoutout_text.present?) %>">
<i class="fa-solid fa-bullhorn"></i>
Expand Down
6 changes: 4 additions & 2 deletions app/views/events/recipients.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@
form to add the shout-out text (the toggle is already saved). %>
<% if recipient_shoutout_options.any? %>
<%= form_with url: feature_recipient_shoutout_event_path(@event), method: :post,
data: { turbo: false }, class: "flex items-center gap-2 print:hidden" do %>
class: "flex items-center gap-2 print:hidden" do %>
<div class="min-w-[220px]" data-controller="searchable-select" data-searchable-select-mode-value="person">
<%= select_tag :registration_id,
options_for_select(recipient_shoutout_options),
Expand All @@ -216,7 +216,9 @@
class: "ts-flat w-full bg-white border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-fuchsia-500 focus:border-fuchsia-500",
data: { searchable_select_target: "select" } %>
</div>
<button type="submit" class="<%= button_classes(:utility_outline, extra: "whitespace-nowrap") %>">
<button type="submit"
class="<%= button_classes(:utility_outline, extra: "whitespace-nowrap") %>"
data-turbo-submits-with="Adding…">
<i class="fa-solid fa-plus"></i> Add shoutout
</button>
<% end %>
Expand Down
5 changes: 4 additions & 1 deletion spec/requests/events_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3882,8 +3882,9 @@ def ce_chip_text
post feature_recipient_shoutout_event_path(event), params: { registration_id: registration.id }

expect(registration.reload.shoutout).to be(true)
expect(response).to have_http_status(:see_other)
expect(response).to redirect_to(
edit_event_registration_path(registration, return_to: "recipients", anchor: "shout-out")
edit_event_registration_path(registration, return_to: "recipients", scroll_to: "shout-out")
)
end

Expand Down Expand Up @@ -3917,6 +3918,7 @@ def ce_chip_text
it "returns to the recipients page with an alert when no recipient is chosen" do
post feature_recipient_shoutout_event_path(event), params: { registration_id: "" }

expect(response).to have_http_status(:see_other)
expect(response).to redirect_to(recipients_event_path(event))
end

Expand All @@ -3927,6 +3929,7 @@ def ce_chip_text
post feature_recipient_shoutout_event_path(event), params: { registration_id: registration.id }

expect(registration.reload.shoutout).to be(false)
expect(response).to have_http_status(:see_other)
expect(response).to redirect_to(recipients_event_path(event))
expect(flash[:alert]).to match(/transferred out|locked/i)
end
Expand Down
72 changes: 72 additions & 0 deletions spec/system/events_recipient_shoutout_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require "rails_helper"

RSpec.describe "Events recipient shout-out form", type: :system, js: true do
let(:admin) { create(:user, :admin) }
let(:event) { create(:event, start_date: 1.month.from_now, end_date: 1.month.from_now + 1.day) }
let(:registration_form) { create(:form, name: "Registration") }
let(:scholarship_form) { create(:form, :scholarship) }
let(:existing_recipient) { create(:person, first_name: "Alex", last_name: "Smith", shoutout_text: "Proud to serve.") }
let!(:existing_registration) do
create(
:event_registration,
event: event,
registrant: existing_recipient,
status: "registered",
scholarship_requested: true,
shoutout: true
)
end
let(:applicant) { create(:person, first_name: "Tara", last_name: "Gallagher") }
let!(:registration) do
create(:event_registration, event: event, registrant: applicant, status: "registered", scholarship_requested: true)
end

before do
create(:event_form, :registration, event: event, form: registration_form)
create(:event_form, :scholarship, event: event, form: scholarship_form)
sign_in admin
end

def select_recipient(registration_id)
find("select#registration_id", visible: :all).find("option[value='#{registration_id}']").select_option
page.execute_script("document.getElementById('registration_id')?.tomselect?.setValue('#{registration_id}')")
end

it "features the chosen recipient via Turbo and redirects to their registration edit form" do
visit recipients_event_path(event)

within("#shout-outs") do
expect(page).to have_button("Add shoutout")
select_recipient(registration.id)
click_button "Add shoutout"
end

expect(page).to have_current_path(
edit_event_registration_path(registration, return_to: "recipients")
)
expect(page).to have_css("#shout-out")
expect(page).to have_content("Featured on the recipients page — review their shout-out text below.")
expect(registration.reload.shoutout).to be(true)
end

it "shows a warning alert when submitting without choosing a recipient" do
visit recipients_event_path(event)

within("#shout-outs") do
click_button "Add shoutout"
end

expect(page).to have_current_path(recipients_event_path(event))
expect(page).to have_content("Choose a recipient to feature.")
expect(registration.reload.shoutout).to be(false)
end

it "disables the submit button while submission is in progress" do
visit recipients_event_path(event)

within("#shout-outs") do
button = find("button[type='submit']")
expect(button["data-turbo-submits-with"]).to eq("Adding…")
end
end
end
Loading