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
13 changes: 12 additions & 1 deletion app/controllers/event_registrations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class EventRegistrationsController < ApplicationController
require "csv"

# show redirects to slug URL; kept for backwards compatibility
before_action :set_event_registration, only: [ :show, :edit, :update, :destroy, :update_onboarding ]
before_action :set_event_registration, only: [ :show, :edit, :update, :destroy, :update_onboarding, :toggle_certificate_issued ]

def index
authorize!
Expand Down Expand Up @@ -155,6 +155,17 @@ def update_onboarding
end
end

# Inline toggle of the "certificate issued" flag from the registrants roster.
# Replaces just the cell so the whole roster doesn't re-render on every toggle.
def toggle_certificate_issued
authorize! @event_registration, to: :toggle_certificate_issued?
@event_registration.update!(certificate_issued: ActiveModel::Type::Boolean.new.cast(params[:value]))
respond_to do |format|
format.turbo_stream
format.html { redirect_to helpers.registrants_event_row_path(@event_registration.event, @event_registration.id) }
end
end

def confirm
@event_registration = EventRegistration.includes(registrant: :user, event: :location).find(params[:id])
authorize! @event_registration, to: :confirm?
Expand Down
3 changes: 3 additions & 0 deletions app/policies/event_registration_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ def unlink_organization? = admin?
# Editing the onboarding matrix is an admin management action; event owners
# (the event's creator) manage their own events' onboarding too.
def update_onboarding? = admin? || event_owner?
# Marking a certificate issued from the registrants roster is an event-management
# action, so mirror the roster's audience (admins and the event's owner).
def toggle_certificate_issued? = admin? || event_owner?


relation_scope do |relation|
Expand Down
10 changes: 10 additions & 0 deletions app/views/event_registrations/_certificate_issued_toggle.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<%# Inline "certificate issued" toggle, targeted by id for turbo replacement. %>
<span id="<%= dom_id(registration, :certificate_issued) %>" class="inline-block">
<%= form_with url: toggle_certificate_issued_event_registration_path(registration), method: :patch, class: "inline" do %>
<%= hidden_field_tag :value, "0" %>
<%= check_box_tag :value, "1", registration.certificate_issued?,
onchange: "this.form.requestSubmit()",
"aria-label": "Certificate issued",
class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-200 cursor-pointer" %>
<% end %>
</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<%# Replace just the toggled cell β€” avoids re-rendering the whole roster. %>
<%= turbo_stream.replace dom_id(@event_registration, :certificate_issued) do %>
<%= render "event_registrations/certificate_issued_toggle", registration: @event_registration %>
<% end %>
23 changes: 23 additions & 0 deletions app/views/events/_registrants_results.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@
<span data-column-toggle-target="knob" class="absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform"></span>
</span>
</label>

<%# Off by default β€” Certificate issued is hidden until the admin reveals it. %>
<label class="inline-flex items-center gap-2 cursor-pointer select-none"
data-controller="column-toggle" data-column-toggle-group-value="certificate">
<span class="text-sm text-gray-500">Certificate</span>

<input
type="checkbox"
data-column-toggle-target="toggle"
data-action="column-toggle#toggle"
class="sr-only"
>

<span class="relative inline-block w-9 h-5">
<span data-column-toggle-target="track" class="absolute inset-0 rounded-full bg-gray-300 transition-colors"></span>
<span data-column-toggle-target="knob" class="absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform"></span>
</span>
</label>
</div>
</div>

Expand Down Expand Up @@ -193,6 +211,8 @@
<th class="px-4 py-2 text-center text-sm font-semibold text-gray-700" aria-sort="none">
<%= render "shared/sortable_header", label: "Status", index: status_index %>
</th>
<%# Off by default; kept out of the sortable index range (last data column). %>
<th class="px-4 py-2 text-center text-sm font-semibold text-gray-700 hidden" data-column-toggle-col="certificate">Certificate</th>
<th class="px-4 py-2"></th>
</tr>
</thead>
Expand Down Expand Up @@ -437,6 +457,9 @@
issues: readiness.status_issues,
subtext: readiness.status_reason %>
</td>
<td class="px-4 py-2 text-sm text-center hidden" data-column-toggle-col="certificate">
<%= render "event_registrations/certificate_issued_toggle", registration: registration %>
</td>
<td class="px-4 py-2 text-right text-sm"><%= link_to "Edit", edit_event_registration_path(registration, return_to: "registrants"), class: "text-gray-500 hover:text-gray-700 underline", data: { turbo_frame: "_top" } %></td>
</tr>
<% end %>
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
post :create_organization
delete :unlink_organization
patch :update_onboarding
patch :toggle_certificate_issued
end
resources :comments, only: [ :index, :create, :update ]
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class AddCertificateIssuedToEventRegistrations < ActiveRecord::Migration[8.1]
def up
add_column :event_registrations, :certificate_issued, :boolean, default: false, null: false
end

def down
remove_column :event_registrations, :certificate_issued, if_exists: true
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.1].define(version: 2026_08_07_175302) do
ActiveRecord::Schema[8.1].define(version: 2026_08_08_181446) do
create_table "action_text_mentions", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.bigint "action_text_rich_text_id", null: false
t.datetime "created_at", null: false
Expand Down Expand Up @@ -479,6 +479,7 @@
end

create_table "event_registrations", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.boolean "certificate_issued", default: false, null: false
t.datetime "certificate_sent_at"
t.string "checkout_session_id"
t.boolean "completed_day_1", default: false, null: false
Expand Down
34 changes: 34 additions & 0 deletions spec/requests/event_registrations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,32 @@ def toggle_day(field, value)
end
end

describe "PATCH /event_registrations/:id/toggle_certificate_issued" do
let(:registration) { create(:event_registration, event: event) }

def toggle_certificate(value)
patch toggle_certificate_issued_event_registration_path(registration),
params: { value: value },
headers: { "Accept" => "text/vnd.turbo-stream.html" }
end

it "marks the certificate issued" do
toggle_certificate("1")
expect(registration.reload.certificate_issued).to be(true)
end

it "clears the flag when unchecked" do
registration.update!(certificate_issued: true)
toggle_certificate("0")
expect(registration.reload.certificate_issued).to be(false)
end

it "replaces just the toggled cell in the turbo stream" do
toggle_certificate("1")
expect(response.body).to include("certificate_issued_event_registration_#{registration.id}")
end
end

describe "POST /event_registrations" do
it "creates registration and redirects admin to confirm page" do
expect {
Expand Down Expand Up @@ -1093,6 +1119,14 @@ def details_open?(body, heading)
end
end

describe "PATCH /event_registrations/:id/toggle_certificate_issued" do
it "is forbidden for the registrant themselves" do
patch toggle_certificate_issued_event_registration_path(existing_registration), params: { value: "1" }
expect(response).to redirect_to(root_path)
expect(existing_registration.reload.certificate_issued).to be(false)
end
end

describe "POST /event_registrations" do
context "when no registration exists yet" do
it "creates a new EventRegistration" do
Expand Down