Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1ed1a54
feat: add check_in_code and source columns for self check-in
mroderick Jul 3, 2026
40794bc
feat: add check_in_code generation from EFF word list
mroderick Jul 3, 2026
7e5f038
feat: add CheckInPdf service for landscape QR check-in PDF
mroderick Jul 3, 2026
d678292
feat: add admin check-in controller with instructions and PDF download
mroderick Jul 3, 2026
15bf5ac
feat: add Check-in button to admin event/workshop toolbar
mroderick Jul 3, 2026
65941f0
feat: add public check-in flow with role selection and confirmation
mroderick Jul 3, 2026
8a0ba63
feat: set source column in all invitation-creation code paths
mroderick Jul 3, 2026
2050f2d
fix: update schema.rb with new check_in_code and source columns
mroderick Jul 3, 2026
f094dd6
fix: add routes.default_url_options for dev route helpers
mroderick Jul 3, 2026
d58e76c
fix: use AddressPresenter for PDF venue address
mroderick Jul 3, 2026
9a29df3
fix: show full URL in PDF, remove bounding box that caused wrapping
mroderick Jul 3, 2026
7a9a764
fix: add Bootstrap container wrapper to public check-in views
mroderick Jul 3, 2026
7577765
fix: add venue and chapter info to check-in page
mroderick Jul 3, 2026
4152eb1
fix: show full date with year, start and end time on check-in page
mroderick Jul 3, 2026
6688278
fix: show time as 'from HH:MM until HH:MM' on one line
mroderick Jul 3, 2026
69944c9
feat: add codebar logo to check-in PDF
mroderick Jul 3, 2026
93d1c71
fix: enlarge and center codebar logo in PDF
mroderick Jul 3, 2026
a1724b9
fix: enlarge logo to 350pt, increase top margin to 40pt
mroderick Jul 3, 2026
e50b5dd
fix: use SVG logo via prawn-svg for crisp vector rendering
mroderick Jul 3, 2026
14c135a
fix: add initializer to include prawn-svg extension in Prawn::Document
mroderick Jul 3, 2026
f4db6b5
fix: double logo size and center horizontally
mroderick Jul 3, 2026
f186ac9
fix: remove text, center SVG mark only
mroderick Jul 3, 2026
d49600f
fix: use bounding box for SVG mark to prevent overflow to second page
mroderick Jul 3, 2026
b621b9d
fix: double mark to 240pt, remove top margin
mroderick Jul 3, 2026
70343ed
revert: restore 120pt mark and 10pt margin
mroderick Jul 3, 2026
10c415b
fix: add container wrapper to admin check-in view
mroderick Jul 6, 2026
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
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ end

gem 'rollbar'
gem 'scout_apm'
gem 'rqrcode'
gem 'prawn'
gem 'prawn-svg', '~> 0.35'

gem 'carrierwave-aws', '~> 1.6'
gem 'sitemap_generator', '~> 7.0'
25 changes: 25 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ GEM
carrierwave (>= 2.0, < 4)
childprocess (5.1.0)
logger (~> 1.5)
chunky_png (1.4.0)
cocoon (1.2.15)
coderay (1.1.3)
coffee-script (2.4.1)
Expand Down Expand Up @@ -351,6 +352,7 @@ GEM
parser (3.3.11.1)
ast (~> 2.4.1)
racc
pdf-core (0.9.0)
pg (1.6.3-aarch64-linux)
pg (1.6.3-arm64-darwin)
pg (1.6.3-x86_64-darwin)
Expand All @@ -364,6 +366,14 @@ GEM
popper_js (2.11.8)
pp (0.6.3)
prettyprint
prawn (2.4.0)
pdf-core (~> 0.9.0)
ttfunk (~> 1.7)
prawn-svg (0.40.3)
css_parser (>= 1.17.1, < 4)
matrix (~> 0.4.2)
prawn (>= 0.11.1, < 3)
rexml (>= 3.4.2, < 4)
premailer (1.29.0)
addressable
css_parser (>= 1.19.0)
Expand Down Expand Up @@ -464,6 +474,10 @@ GEM
rolify (6.0.1)
rollbar (3.8.0)
rouge (4.1.3)
rqrcode (3.2.0)
chunky_png (~> 1.0)
rqrcode_core (~> 2.0)
rqrcode_core (2.1.0)
rspec-collection_matchers (1.2.1)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (3.13.6)
Expand Down Expand Up @@ -574,6 +588,7 @@ GEM
tilt (2.7.0)
timeout (0.6.1)
tsort (0.2.0)
ttfunk (1.7.0)
turbo-rails (2.0.23)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
Expand Down Expand Up @@ -668,6 +683,8 @@ DEPENDENCIES
parallel_tests
pg
pickadate-rails
prawn
prawn-svg (~> 0.35)
premailer-rails
pry-byebug
pry-rails
Expand All @@ -682,6 +699,7 @@ DEPENDENCIES
reline
rolify
rollbar
rqrcode
rspec-collection_matchers
rspec-rails
rubocop
Expand Down Expand Up @@ -747,6 +765,7 @@ CHECKSUMS
carrierwave (3.1.3) sha256=b99324c5ea63c55ce0776bc0e997c1c70cdd3a490a7458d23a8978930c76541a
carrierwave-aws (1.6.1) sha256=ad9eb8cc677c0b9371bd3534b53689ca731ddc6d04fe764e131e298f36adb13b
childprocess (5.1.0) sha256=9a8d484be2fd4096a0e90a0cd3e449a05bc3aa33f8ac9e4d6dcef6ac1455b6ec
chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe
cocoon (1.2.15) sha256=d08f14e69653287d7a060ee43389b8c824e55191dffbca0c5c586f38ef491f0d
coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b
coffee-script (2.4.1) sha256=82fe281e11b93c8117b98c5ea8063e71741870f1c4fbb27177d7d6333dd38765
Expand Down Expand Up @@ -843,6 +862,7 @@ CHECKSUMS
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
parallel_tests (5.7.0) sha256=3f1762c46ca2c223b8af8ef877217f9d76974e191bfa934f2580b58bcf1d005c
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
pdf-core (0.9.0) sha256=4f368b2f12b57ec979872d4bf4bd1a67e8648e0c81ab89801431d2fc89f4e0bb
pg (1.6.3-aarch64-linux) sha256=0698ad563e02383c27510b76bf7d4cd2de19cd1d16a5013f375dd473e4be72ea
pg (1.6.3-arm64-darwin) sha256=7240330b572e6355d7c75a7de535edb5dfcbd6295d9c7777df4d9dddfb8c0e5f
pg (1.6.3-x86_64-darwin) sha256=ee2e04a17c0627225054ffeb43e31a95be9d7e93abda2737ea3ce4a62f2729d6
Expand All @@ -851,6 +871,8 @@ CHECKSUMS
playwright-ruby-client (1.59.0) sha256=b9e66233ed7b83e917980badc27ecd233e244503c74772a3854dbd5d41438c21
popper_js (2.11.8) sha256=f4b0be717fc0d50bdb3dbbc55788525a9e0e8f640b76c9971fc34ee609eadbd2
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
prawn (2.4.0) sha256=82062744f7126c2d77501da253a154271790254dfa8c309b8e52e79bc5de2abd
prawn-svg (0.40.3) sha256=26237d3a7268143b5ffc6c1354ab93d0a4b81366573c749b6316e6f6f7733c0c
premailer (1.29.0) sha256=015f30c520701f3d47fd898886a3eaf4e5171efcc5fd239b872efd1ba61d3da0
premailer-rails (1.12.0) sha256=c13815d161b9bc7f7d3d81396b0bb0a61a90fa9bd89931548bf4e537c7710400
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
Expand Down Expand Up @@ -887,6 +909,8 @@ CHECKSUMS
rolify (6.0.1) sha256=fe45fbf70c4033ccada6f9ccc02b946eeaad044ea5d3e38546ae408d3adeb5b6
rollbar (3.8.0) sha256=655d2783c84c854634f4a1045f34ff8d390596ee4c481f01fe1b214423047a3e
rouge (4.1.3) sha256=9c8663db26e05e52b3b0286daacae73ebb361c1bd31d7febd8c57087faa0b9a5
rqrcode (3.2.0) sha256=64c1494ca6bb67d731330f38b50e3fd09eeab4f5dcd04b608e21218d1d0b9542
rqrcode_core (2.1.0) sha256=f303b85df89c1b8fc5ee8dc19808c9dc4330e6329b660d99d4a8cbb36ca13051
rspec-collection_matchers (1.2.1) sha256=ff7626d2bbf16ac237fbb46439694c78b7d9c6dc49c4b216df437968133b56a2
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
Expand Down Expand Up @@ -929,6 +953,7 @@ CHECKSUMS
tilt (2.7.0) sha256=0d5b9ba69f6a36490c64b0eee9f6e9aad517e20dcc848800a06eb116f08c6ab3
timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
ttfunk (1.7.0) sha256=2370ba484b1891c70bdcafd3448cfd82a32dd794802d81d720a64c15d3ef2a96
turbo-rails (2.0.23) sha256=ee0d90733aafff056cf51ff11e803d65e43cae258cc55f6492020ec1f9f9315f
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
tzinfo-data (1.2026.2) sha256=7db0d3d3d53b8d7601fc183fccc8c6d056a3004e14eb59ea995bf6aec4ae10bc
Expand Down
52 changes: 52 additions & 0 deletions app/assets/images/check_in/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions app/controllers/admin/check_ins_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

class Admin::CheckInsController < Admin::ApplicationController
before_action :load_parent

def show
authorize @parent
@parent.generate_check_in_code! if @parent.check_in_code.blank?

respond_to do |format|
format.html
format.pdf do
pdf = CheckInPdf.new(@parent).render
send_data pdf,
filename: "check-in-#{@parent.to_param}.pdf",
type: "application/pdf",
disposition: "attachment"
end
end
end

private

def load_parent
if params[:event_id]
@parent = Event.find_by!(slug: params[:event_id])
elsif params[:workshop_id]
@parent = Workshop.find(params[:workshop_id])
end
end
end
4 changes: 2 additions & 2 deletions app/controllers/admin/invitation_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Admin::InvitationController < Admin::ApplicationController

def update
invitation = Invitation.find_by(token: params[:invitation][:id])
invitation.update(attending: true, verified: true, verified_by: current_user)
invitation.update(attending: true, verified: true, verified_by: current_user, source: "admin")

EventInvitationMailer.attending(invitation.event, invitation.member, invitation).deliver_now

Expand All @@ -17,7 +17,7 @@ def update

def verify
invitation = Invitation.find_by(token: params[:invitation_id])
invitation.update(verified: true, verified_by_id: current_user.id)
invitation.update(verified: true, verified_by_id: current_user.id, source: "admin")

EventInvitationMailer.attending(invitation.event, invitation.member, invitation).deliver_now

Expand Down
5 changes: 3 additions & 2 deletions app/controllers/admin/invitations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def update_attendance(attending:, attended:)
end

def update_to_attended
@invitation.update(attended: true)
@invitation.update(attended: true, source: "admin")
end

def update_to_unattended
Expand All @@ -50,7 +50,8 @@ def update_to_attending
attending: true,
rsvp_time: Time.zone.now,
automated_rsvp: true,
last_overridden_by_id: current_user.id
last_overridden_by_id: current_user.id,
source: "admin"
)

{
Expand Down
102 changes: 102 additions & 0 deletions app/controllers/check_ins_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# frozen_string_literal: true

class CheckInsController < ApplicationController
before_action :load_parent
before_action :store_referer_path, only: [:new]
before_action :authenticate_member!, only: [:new]

def new
@invitation = find_invitation
@suggested_role = infer_role
@already_checked_in = already_checked_in?(@invitation)
end

def create
role = params[:role]
invitation = find_or_create_invitation(role)
mark_attended(invitation)
redirect_to check_in_confirm_path
end

def confirm
@invitation = find_invitation
end

private

def load_parent
@parent = Event.find_by(check_in_code: params[:code]) ||
Workshop.find_by(check_in_code: params[:code])
raise ActiveRecord::RecordNotFound unless @parent
end

helper_method :check_in_submit_path, :check_in_confirm_path

def check_in_submit_path
@parent.is_a?(Event) ?
check_in_e_path(code: @parent.check_in_code) :
check_in_w_path(code: @parent.check_in_code)
end

def check_in_confirm_path
@parent.is_a?(Event) ?
check_in_e_confirm_path(code: @parent.check_in_code) :
check_in_w_confirm_path(code: @parent.check_in_code)
end

def store_referer_path
session[:referer_path] = request.path unless logged_in?
end

def already_checked_in?(invitation)
return false unless invitation

if @parent.is_a?(Event)
invitation.verified?
else
invitation.attended?
end
end

def find_invitation
if @parent.is_a?(Event)
Invitation.find_by(event: @parent, member: current_user)
else
WorkshopInvitation.find_by(workshop: @parent, member: current_user)
end
end

def find_or_create_invitation(role)
if @parent.is_a?(Event)
Invitation.find_or_create_by!(event: @parent, member: current_user, role: role)
else
WorkshopInvitation.find_or_create_by!(workshop: @parent, member: current_user, role: role)
end
end

def mark_attended(invitation)
attrs = { attending: true, source: "check_in" }
if @parent.is_a?(Event)
attrs[:verified] = true
else
attrs[:attended] = true
end
invitation.update!(attrs)
end

def infer_role
return @invitation.role if @invitation.present?

groups = current_user.groups
student = groups.students.any?
coach = groups.coaches.any?

if student && !coach
"Student"
elsif coach && !student
"Coach"
else
nil
end
end
end
4 changes: 2 additions & 2 deletions app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def rsvp
ticket = Services::Ticket.new(request, params)
member = Member.find_by(email: ticket.email)
invitation = member.invitations.where(event: @event, role: 'Student').first
invitation ||= Invitation.create(event: @event, member: member, role: 'Student')
invitation ||= Invitation.create(event: @event, member: member, role: 'Student', source: 'email')

invitation.update(attending: true)
head :ok
Expand All @@ -63,7 +63,7 @@ def latest_model_updated

def find_invitation_and_redirect_to_event(role)
set_event
@invitation = Invitation.find_or_create_by(event: @event, member: current_user, role: role)
@invitation = Invitation.find_or_create_by(event: @event, member: current_user, role: role, source: 'email')
redirect_to event_invitation_path(@event, @invitation)
end

Expand Down
3 changes: 2 additions & 1 deletion app/controllers/workshops_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def find_attending_invitation(workshop, user)
def find_or_create_invitation(workshop, user, role)
WorkshopInvitation.find_or_create_by(workshop: workshop,
member: user,
role: role)
role: role,
source: 'email')
end

def user_attending_or_waitlisted?(workshop, user)
Expand Down
Loading