Skip to content
Merged
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 @@ -114,7 +114,7 @@ AWBW Portal (Rails 8.1)
- **Root level** (~48 controllers): Workshops, stories, resources, events, people, organizations, etc.
- **`admin/`**: HomeController, AnalyticsController, AhoyActivitiesController
- **`api/v1/`**: ApiController base, Authentications, Workshops, Quotes, Resources
- **`events/`**: Registrations sub-resource
- **`events/`**: Registrations sub-resource (create/destroy + slug-based show at `/registration/:slug`)
- **Devise overrides**: Registrations, Confirmations, Passwords

### Base Controller Pattern
Expand Down
7 changes: 4 additions & 3 deletions app/controllers/event_registrations_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class EventRegistrationsController < ApplicationController
require "csv"

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

def index
Expand Down Expand Up @@ -30,6 +30,7 @@ def index

def show
authorize! @event_registration
redirect_to registration_ticket_path(@event_registration.slug), status: :moved_permanently
end

def new
Expand Down Expand Up @@ -67,7 +68,7 @@ def create
redirect_to manage_event_path(@event_registration.event),
notice: "Registration created."
else
redirect_to @event_registration,
redirect_to registration_ticket_path(@event_registration.slug),
notice: "Registration created."
end
}
Expand Down Expand Up @@ -100,7 +101,7 @@ def update
if params[:return_to] == "manage"
redirect_to manage_event_path(@event_registration.event), notice: "Registration was successfully updated.", status: :see_other
else
redirect_to @event_registration, notice: "Registration was successfully updated.", status: :see_other
redirect_to registration_ticket_path(@event_registration.slug), notice: "Registration was successfully updated.", status: :see_other
end
}
end
Expand Down
7 changes: 4 additions & 3 deletions app/controllers/events/public_registrations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def create
)

if result.success?
redirect_to event_registration_path(result.event_registration),
redirect_to registration_ticket_path(result.event_registration.slug),
notice: "You have been successfully registered!"
else
@form_fields = @form.form_fields.where(status: :active).reorder(position: :asc)
Expand All @@ -63,14 +63,15 @@ def create
def show
authorize! :public_registration, to: :show?

registration = EventRegistration.find_by!(slug: params[:reg], event_id: @event.id)

@form = registration_form
unless @form
redirect_to event_path(@event), alert: "Registration form not found."
return
end


@person_form = @form.person_forms.find_by(person: params[:person_id])
@person_form = @form.person_forms.find_by(person: registration.registrant)
unless @person_form
redirect_to event_path(@event), alert: "No registration form submission found."
return
Expand Down
45 changes: 41 additions & 4 deletions app/controllers/events/registrations_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
module Events
class RegistrationsController < ApplicationController
before_action :authenticate_user!
before_action :set_event
before_action :set_registrant
before_action :authenticate_user!, only: [ :create, :destroy ]
before_action :set_event, only: [ :create, :destroy ]
before_action :set_registrant, only: [ :create, :destroy ]
before_action :set_event_registration, only: [ :show, :resend_confirmation, :cancel, :reactivate ]

def show
authorize! @event_registration, to: :show_public?
end

def resend_confirmation
authorize! @event_registration, to: :show_public?
EventMailer.event_registration_confirmation(@event_registration).deliver_later
redirect_to registration_ticket_path(@event_registration.slug), notice: "Confirmation email sent."
end

def cancel
authorize! @event_registration, to: :show_public?

if @event_registration.active?
@event_registration.update!(status: "cancelled")
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."
end
end

def reactivate
authorize! @event_registration, to: :show_public?

if @event_registration.status == "cancelled"
@event_registration.update!(status: "registered")
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."
end
end

def create
@event_registration = @event.event_registrations.new(registrant: @registrant)
Expand All @@ -12,7 +45,7 @@ def create
success = "You have successfully registered for this event."
respond_to do |format|
format.turbo_stream { flash.now[:notice] = success }
format.html { redirect_to @event_registration, notice: success }
format.html { redirect_to registration_ticket_path(@event_registration.slug), notice: success }
end
else
error = @event_registration.errors.full_messages.to_sentence
Expand Down Expand Up @@ -78,5 +111,9 @@ def create_person_for_current_user
current_user.update!(person: person)
person
end

def set_event_registration
@event_registration = EventRegistration.find_by!(slug: params[:slug])
end
end
end
4 changes: 4 additions & 0 deletions app/decorators/event_registration_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ def title
def detail(length: nil)
end

def link_target
h.registration_ticket_path(slug)
end

def default_display_image
return event.primary_asset.file if event.respond_to?(:primary_asset) && event.primary_asset&.file&.attached?
"theme_default.png"
Expand Down
2 changes: 1 addition & 1 deletion app/mailers/event_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def event_registration_confirmation(event_registration)
@notification_type = "Event registration confirmation"

@time_zone = @person.user&.time_zone || Time.zone.name
@event_url = event_url(@event)
@event_url = event_url(@event, reg: @event_registration.slug)
@organization_name = ENV.fetch("ORGANIZATION_NAME", "AWBW")
@organization_website = ENV.fetch("ORGANIZATION_WEBSITE", root_url)

Expand Down
10 changes: 10 additions & 0 deletions app/models/event_registration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class EventRegistration < ApplicationRecord

accepts_nested_attributes_for :comments, reject_if: proc { |attrs| attrs["body"].blank? }

before_create :generate_slug

ACTIVE_STATUSES = %w[ registered attended incomplete_attendance ].freeze
INACTIVE_STATUSES = %w[ cancelled no_show ].freeze
ATTENDANCE_STATUSES = (ACTIVE_STATUSES + INACTIVE_STATUSES).freeze
Expand All @@ -17,6 +19,7 @@ class EventRegistration < ApplicationRecord
validates :registrant_id, uniqueness: { scope: :event_id }
validates :event_id, presence: true
validates :status, inclusion: { in: ATTENDANCE_STATUSES }, allow_nil: false
validates :slug, uniqueness: true, allow_nil: true

# Scopes
scope :registrant_name, ->(registrant_name) { joins(:registrant).where(
Expand Down Expand Up @@ -136,4 +139,11 @@ def create_refund_payments
currency: "usd"
)
end

def generate_slug
loop do
self.slug = SecureRandom.urlsafe_base64(16)
break unless EventRegistration.exists?(slug: slug)
end
end
end
3 changes: 2 additions & 1 deletion app/policies/event_registration_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ def index? = admin?
def create? = admin? || owner?
def update? = admin? || owner?
def destroy? = record.persisted? && (admin? || owner?)
def show? = true
def show? = admin?
def show_public? = true


relation_scope do |relation|
Expand Down
3 changes: 3 additions & 0 deletions app/services/event_registration_form_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ def build_qualitative_fields(form, position)
key: "referral_source", group: "qualitative", required: false)
position = add_field(form, position, "What motivates you to attend this training?", :free_form_input_paragraph,
key: "training_motivation", group: "qualitative", required: false)
position = add_field(form, position, "Are you interested in learning more about upcoming trainings or resources?", :multiple_choice_radio,
key: "interested_in_more", group: "qualitative", required: true,
options: %w[Yes No])

position
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@

<p>
<% if @event_registration.persisted? %>
<a href="<%= event_registration_url(@event_registration) %>" class="button">View registration</a>
<a href="<%= registration_ticket_url(@event_registration.slug) %>" class="button">View registration</a>
<% end %>
<a href="<%= @event_url %>" class="button">View event</a>
</p>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Videoconference URL: <%= @event.videoconference_url %>
<%= @event.detail %>
<% end %>

View your registration:
<%= registration_ticket_url(@event_registration.slug) %>

View the event page:
<%= @event_url %>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
<% content_for(:page_bg_class, "admin-or-owner") %>
<div class="flex flex-wrap justify-end gap-2 mb-4">
<%= link_to "Home", root_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %>
<% if allowed_to?(:index?, EventRegistration) %>
<%= link_to "Manage Registrants", manage_event_path(@event_registration.event), class: "admin-only bg-blue-100 text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %>
<% end %>
</div>
<div class="max-w-lg mx-auto">
<%= link_to "← Back to Event", event_path(@event_registration.event), class: "text-sm text-gray-500 hover:text-gray-700 mb-4 inline-block" %>
<%= link_to "← Back to Event", event_path(event_registration.event, reg: event_registration.slug), class: "text-sm text-gray-500 hover:text-gray-700 mb-4 inline-block" %>

<!-- Ticket Container -->
<div class="bg-white rounded-xl shadow-lg overflow-hidden border border-gray-200">
Expand Down Expand Up @@ -42,19 +35,19 @@
<div class="text-center space-y-4">
<div class="inline-block rounded-lg overflow-hidden border border-gray-200 shadow-sm">
<%= render "assets/display_image",
resource: @event_registration.event,
resource: event_registration.event,
width: 48, height: 32,
variant: :index,
link_to_object: true,
file: @event_registration.event.decorate.display_image %>
link: event_path(event_registration.event, reg: event_registration.slug),
file: event_registration.event.decorate.display_image %>
</div>

<div>
<% if @event_registration.event.respond_to?(:pre_title) && @event_registration.event.pre_title.present? %>
<p class="text-base font-semibold text-gray-700 mb-1" style="font-family: 'Telefon Bold', sans-serif"><%= @event_registration.event.pre_title %></p>
<% if event_registration.event.respond_to?(:pre_title) && event_registration.event.pre_title.present? %>
<p class="text-base font-semibold text-gray-700 mb-1" style="font-family: 'Telefon Bold', sans-serif"><%= event_registration.event.pre_title %></p>
<% end %>
<h2 class="text-3xl font-bold text-green-800 leading-tight" style="font-family: Lato, sans-serif">
<%= link_to @event_registration.event.title, event_path(@event_registration.event), class: "hover:underline" %>
<%= link_to event_registration.event.title, event_path(event_registration.event, reg: event_registration.slug), class: "hover:underline" %>
</h2>
</div>
</div>
Expand All @@ -71,9 +64,9 @@
<p class="text-gray-500 uppercase tracking-wide text-xs">Registrant</p>
<p class="font-medium text-gray-900 text-lg">
<% if user_signed_in? %>
<%= link_to @event_registration.registrant.full_name, person_path(@event_registration.registrant), class: "text-blue-700 hover:text-blue-900 underline" %>
<%= link_to event_registration.registrant.full_name, person_path(event_registration.registrant), class: "text-blue-700 hover:text-blue-900 underline" %>
<% else %>
<%= @event_registration.registrant.full_name %>
<%= event_registration.registrant.full_name %>
<% end %>
</p>
</div>
Expand All @@ -82,33 +75,33 @@
<div class="text-center space-y-2">

<div class="text-xl font-bold text-blue-900 uppercase" style="font-family: Lato, sans-serif">
<%= @event_registration.event.decorate.times(display_day: true, display_date: true, styled: true) %>
<%= event_registration.event.decorate.times(display_day: true, display_date: true, styled: true) %>
</div>

<% if @event_registration.event.decorate.labelled_cost.present? %>
<% if event_registration.event.decorate.labelled_cost.present? %>
<div class="text-lg font-bold text-blue-900 uppercase" style="font-family: Lato, sans-serif">
<%= @event_registration.event.decorate.labelled_cost %>
<%= event_registration.event.decorate.labelled_cost %>
</div>
<% end %>

<% if @event_registration.event.location.present? %>
<% if event_registration.event.location.present? %>
<div class="text-base text-gray-700">
<%= @event_registration.event.location.name %>
<%= event_registration.event.location.name %>
</div>
<% end %>

<% if @event_registration.event.autoshow_videoconference_label && @event_registration.event.videoconference_label.present? %>
<div class="text-base text-gray-700"><%= @event_registration.event.videoconference_label %></div>
<% if event_registration.event.autoshow_videoconference_label && event_registration.event.videoconference_label.present? %>
<div class="text-base text-gray-700"><%= event_registration.event.videoconference_label %></div>
<% end %>

<%= render "events/videoconference_link", event: @event_registration.event.decorate, joinable: @event_registration.joinable? %>
<%= render "events/videoconference_link", event: event_registration.event.decorate, joinable: event_registration.joinable? %>


</div>

<% if @event_registration.event.cost_cents.to_i > 0 && !@event_registration.paid? %>
<% paid_cents = @event_registration.payments.successful.sum(:amount_cents) %>
<% due_cents = @event_registration.event.cost_cents - paid_cents %>
<% if event_registration.event.cost_cents.to_i > 0 && !event_registration.paid? %>
<% paid_cents = event_registration.payments.successful.sum(:amount_cents) %>
<% due_cents = event_registration.event.cost_cents - paid_cents %>
<div class="text-center mt-2">
<span class="inline-flex items-center gap-1 px-3 py-1 rounded-full text-sm font-medium bg-amber-100 text-amber-800">
$<%= "%.2f" % (due_cents / 100.0) %> payment is due
Expand All @@ -124,29 +117,26 @@

<!-- Status -->
<div class="mt-4 text-center space-y-2">
<% if @event_registration.checked_in? %>
<% if event_registration.checked_in? %>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
Checked In
</span>
<% elsif @event_registration.respond_to?(:canceled?) && @event_registration.canceled? %>
<% elsif event_registration.status == "cancelled" %>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-100 text-red-800">
Canceled
Registration cancelled
</span>
<% if event_registration.event.registerable? %>
<div>
<%= button_to "Register again", registration_reactivate_path(event_registration.slug),
class: "text-sm text-blue-700 hover:text-blue-900 underline bg-transparent border-0 cursor-pointer p-0" %>
</div>
<% end %>
<% else %>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
Registered on <%= @event_registration.created_at.strftime("%B %-d, %Y") %>
Registered on <%= event_registration.created_at.strftime("%B %-d, %Y") %>
</span>
<% end %>

<% if user_signed_in? %>
<div>
<%= button_to "De-register",
event_registrant_registration_path(event_id: @event_registration.event),
method: :delete,
data: { turbo_confirm: "Are you sure?" },
class: "btn btn-secondary-outline text-sm" %>
</div>
<% end %>
</div>

<!-- CALENDAR LINKS -->
Expand All @@ -155,18 +145,27 @@
Add to Your Calendar
</p>
<div class="flex flex-wrap gap-1 items-center justify-center w-full text-sm text-blue-600 font-medium">
<%= @event_registration.event.decorate.calendar_links %>
<%= event_registration.event.decorate.calendar_links %>
</div>
</div>

<% registration_form = @event_registration.event.forms.find_by(name: "Public Registration") %>
<% if registration_form && registration_form.person_forms.exists?(person: @event_registration.registrant) %>
<div class="mt-4 text-center">
<%= link_to "View Registration Form",
event_public_registration_path(@event_registration.event, person_id: @event_registration.registrant&.id),
class: "text-sm text-blue-700 hover:text-blue-900 underline" %>
</div>
<% end %>
<div class="flex flex-col items-center gap-1">
<% registration_form = event_registration.event.forms.find_by(name: "Public Registration") %>
<% if registration_form && registration_form.person_forms.exists?(person: event_registration.registrant) %>
<%= link_to "View registration form",
event_public_registration_path(event_registration.event, reg: event_registration.slug),
class: "text-xs text-gray-400 hover:text-blue-600 underline" %>
<% end %>
<%= button_to "Resend confirmation email",
registration_resend_confirmation_path(event_registration.slug),
class: "text-xs text-gray-400 hover:text-blue-600 underline bg-transparent border-0 cursor-pointer p-0" %>
<% if event_registration.active? %>
<%= button_to "Cancel registration",
registration_cancel_path(event_registration.slug),
data: { turbo_confirm: "Are you sure you want to cancel your registration?" },
class: "text-xs text-gray-400 hover:text-red-600 underline bg-transparent border-0 cursor-pointer p-0" %>
<% end %>
</div>

</div>

Expand Down
Loading