From 6befeb28da7c34244f81a8a7b2332f6e7084ff15 Mon Sep 17 00:00:00 2001 From: Katya Sarmiento <5871075+Kitkatnik@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:28:34 -0400 Subject: [PATCH 1/2] Embassy passports: admin "Mark Ready" toggle with READY badge on My Plan Adds a new ready state between submitted and received so the embassy can tell attendees their passport is finished and ready for pickup. Admin toggles ready_at on /admin/embassy_applications; attendees see a green-highlighted card with a READY badge on /plan. --- app/assets/stylesheets/application.css | 26 +++++++++++++++++ .../admin/embassy_applications_controller.rb | 14 ++++++++++ .../embassy_applications/delivered.html.erb | 1 + .../admin/embassy_applications/index.html.erb | 22 +++++++++++++++ app/views/plan/_plan_item.html.erb | 17 ++++++++++- config/routes.rb | 2 ++ ...18_add_ready_at_to_embassy_applications.rb | 5 ++++ db/schema.rb | 3 +- .../embassy_applications_controller_test.rb | 23 +++++++++++++++ test/integration/plan_test.rb | 28 +++++++++++++++++++ 10 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20260430192318_add_ready_at_to_embassy_applications.rb diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index d9813d1..efd13da 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -2146,6 +2146,32 @@ hr { .embassy-appointment-card--stamping { border-top-color: #2672B5; } .embassy-appointment-card--pending { border-top-color: #C41C1C; border-top-style: dashed; } .embassy-appointment-card--expired { border-top-color: #7a8189; border-top-style: dashed; opacity: 0.9; } +.embassy-appointment-card--ready { + border-top-color: #16A34A; + background-color: #F0FDF4; + box-shadow: 0 0 0 2px #BBF7D0 inset; +} + +.embassy-ready-badge { + display: inline-block; + background: #16A34A; + color: #fff; + font-size: 0.6875rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + padding: 0.125rem 0.5rem; + border-radius: 9999px; + margin-right: 0.5rem; + vertical-align: middle; +} + +.btn-green { + background: #16A34A; + color: #fff; + border-color: #15803D; +} +.btn-green:hover { background: #15803D; } /* Notary callout ------------------------------------------------------- */ diff --git a/app/controllers/admin/embassy_applications_controller.rb b/app/controllers/admin/embassy_applications_controller.rb index 82f21ea..9465cba 100644 --- a/app/controllers/admin/embassy_applications_controller.rb +++ b/app/controllers/admin/embassy_applications_controller.rb @@ -69,6 +69,20 @@ def unmark_received notice: "Moved #{application.serial} back to the active queue." end + def mark_ready + application = EmbassyApplication.find_by!(serial: params[:id]) + application.update!(ready_at: Time.current) + redirect_back fallback_location: admin_embassy_applications_path, + notice: "Marked #{application.serial} as ready for pickup." + end + + def unmark_ready + application = EmbassyApplication.find_by!(serial: params[:id]) + application.update!(ready_at: nil) + redirect_back fallback_location: admin_embassy_applications_path, + notice: "Cleared ready status for #{application.serial}." + end + def destroy application = EmbassyApplication.find_by!(serial: params[:id]) booking = application.embassy_booking diff --git a/app/views/admin/embassy_applications/delivered.html.erb b/app/views/admin/embassy_applications/delivered.html.erb index d2b047b..e8e1b18 100644 --- a/app/views/admin/embassy_applications/delivered.html.erb +++ b/app/views/admin/embassy_applications/delivered.html.erb @@ -1,4 +1,5 @@ <% content_for :title, "Delivered Passports — Embassy Admin" %> +<% content_for :container_width, "max-w-7xl" %>
diff --git a/app/views/admin/embassy_applications/index.html.erb b/app/views/admin/embassy_applications/index.html.erb index 938eda4..7ccdfc1 100644 --- a/app/views/admin/embassy_applications/index.html.erb +++ b/app/views/admin/embassy_applications/index.html.erb @@ -1,4 +1,5 @@ <% content_for :title, "Submitted Applications — Embassy Admin" %> +<% content_for :container_width, "max-w-7xl" %>
@@ -43,6 +44,7 @@ <%= sort_link("Appointment", "appointment") %> <%= sort_link("Submitted", "submitted") %> Pickup + Status @@ -71,9 +73,29 @@ <% end %> + + <% if application.ready_at.present? %> + Ready + <% else %> + + <% end %> + <%= link_to "View", admin_embassy_application_path(application), class: "text-blue underline mr-3" %> + <% if application.ready_at.present? %> + <%= button_to "Unmark Ready", + unmark_ready_admin_embassy_application_path(application), + method: :patch, + class: "btn btn-ghost mr-2", + form: { class: "inline-block" } %> + <% else %> + <%= button_to "Mark Ready", + mark_ready_admin_embassy_application_path(application), + method: :patch, + class: "btn btn-green mr-2", + form: { class: "inline-block" } %> + <% end %> <%= button_to "Mark Received", mark_received_admin_embassy_application_path(application), method: :patch, diff --git a/app/views/plan/_plan_item.html.erb b/app/views/plan/_plan_item.html.erb index 79578eb..b2527e4 100644 --- a/app/views/plan/_plan_item.html.erb +++ b/app/views/plan/_plan_item.html.erb @@ -15,6 +15,7 @@ if booking.nil? then "stamping" elsif booking.passport_pickup? then "pickup" elsif booking.stamping? then "stamping" + elsif application&.ready_at.present? then "ready" elsif application&.submitted? then "submitted" else "pending" end @@ -71,6 +72,20 @@ class: "btn btn-navy", data: { turbo_frame: "_top" } %> + <% when "ready" %> +

+ Ready + Your passport is finished. Please present your application and notarization paperwork at the side table in the auditorium. + Serial <%= application.serial %> +

+ <% if application.notary_profile.present? %> + <%= render "embassy_applications/notary_callout", notary: application.notary_profile %> + <% end %> + <%= link_to "View / Download PDF", + embassy_application_path(application), + class: "btn btn-navy", + data: { turbo_frame: "_top" } %> + <% when "pickup" %>

Passport pickup. @@ -81,7 +96,7 @@

- <% unless %w[pickup submitted].include?(state) %> + <% unless %w[pickup submitted ready].include?(state) %> <%= button_to "×", plan_item_path(plan_item), method: :delete, diff --git a/config/routes.rb b/config/routes.rb index d2ec3b0..400055d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,6 +33,8 @@ member do patch :mark_received patch :unmark_received + patch :mark_ready + patch :unmark_ready post :schedule_pickup end collection do diff --git a/db/migrate/20260430192318_add_ready_at_to_embassy_applications.rb b/db/migrate/20260430192318_add_ready_at_to_embassy_applications.rb new file mode 100644 index 0000000..9c5c774 --- /dev/null +++ b/db/migrate/20260430192318_add_ready_at_to_embassy_applications.rb @@ -0,0 +1,5 @@ +class AddReadyAtToEmbassyApplications < ActiveRecord::Migration[8.1] + def change + add_column :embassy_applications, :ready_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index d62ec96..14e4405 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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_04_30_033810) do +ActiveRecord::Schema[8.1].define(version: 2026_04_30_192318) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -32,6 +32,7 @@ t.bigint "embassy_booking_id", null: false t.bigint "notary_profile_id" t.datetime "passport_received_at" + t.datetime "ready_at" t.string "serial", null: false t.string "state", default: "draft", null: false t.datetime "submitted_at" diff --git a/test/controllers/admin/embassy_applications_controller_test.rb b/test/controllers/admin/embassy_applications_controller_test.rb index 36b00bc..a6b5088 100644 --- a/test/controllers/admin/embassy_applications_controller_test.rb +++ b/test/controllers/admin/embassy_applications_controller_test.rb @@ -110,4 +110,27 @@ def create_application_for(user, schedule_item) end assert_response :not_found end + + test "mark_ready stamps ready_at on the application" do + sign_in_as users(:jeremy) + assert_nil @application.ready_at + patch mark_ready_admin_embassy_application_path(@application.serial) + assert_not_nil @application.reload.ready_at + assert_match(/ready for pickup/, flash[:notice]) + end + + test "unmark_ready clears ready_at" do + @application.update!(ready_at: Time.current) + sign_in_as users(:jeremy) + patch unmark_ready_admin_embassy_application_path(@application.serial) + assert_nil @application.reload.ready_at + assert_match(/Cleared ready status/, flash[:notice]) + end + + test "non-admins cannot mark ready" do + sign_in_as users(:attendee_one) + patch mark_ready_admin_embassy_application_path(@application.serial) + assert_response :not_found + assert_nil @application.reload.ready_at + end end diff --git a/test/integration/plan_test.rb b/test/integration/plan_test.rb index cc2ee0b..9733e8a 100644 --- a/test/integration/plan_test.rb +++ b/test/integration/plan_test.rb @@ -173,6 +173,34 @@ class PlanTest < ActionDispatch::IntegrationTest end end + test "/plan shows green READY badge on embassy card once application.ready_at is set" do + alice = users(:attendee_one) + passport_block = ScheduleItem.create!( + day: "thu", time_label: "9:00 AM", sort_time: 900, + title: "Passport Block", kind: :embassy, is_public: true, + offers_new_passport: true, new_passport_capacity: 4 + ) + plan_item = alice.plan_items.create!(schedule_item: passport_block) + booking = EmbassyBooking.create!( + user: alice, schedule_item: passport_block, plan_item: plan_item, + mode: "new_passport", state: "confirmed" + ) + application = EmbassyApplication.create!( + embassy_booking: booking, state: "submitted", submitted_at: Time.current, + drawn_question_ids: [], notary_profile_id: nil, + ready_at: Time.current + ) + + sign_in_as alice + get plan_path + + assert_match "embassy-appointment-card--ready", response.body + assert_match "embassy-ready-badge", response.body + assert_match application.serial, response.body + assert_no_match(/aria-label="Cancel appointment"/, response.body, + "cancel button is hidden once the embassy says ready") + end + test "/plan shows activity attendees, contact form for self, and contacts of co-RSVPers" do alice = users(:attendee_one) vic = users(:volunteer_one) From f13816ab47dac7157967b11da185364d29557435 Mon Sep 17 00:00:00 2001 From: Katya Sarmiento <5871075+Kitkatnik@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:35:02 -0400 Subject: [PATCH 2/2] Embassy passports: yellow "Processing" pill for submitted-but-not-ready While an application is submitted but not yet marked ready, surface a yellow Processing pill (mirroring the green Ready pill) on both the admin status column and the attendee's My Plan card, with copy explaining the embassy is processing the passport. --- app/assets/stylesheets/application.css | 7 +++-- .../admin/embassy_applications/index.html.erb | 2 +- app/views/plan/_plan_item.html.erb | 4 ++- test/integration/plan_test.rb | 26 +++++++++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index efd13da..4f83ced 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -2152,9 +2152,9 @@ hr { box-shadow: 0 0 0 2px #BBF7D0 inset; } -.embassy-ready-badge { +.embassy-ready-badge, +.embassy-processing-badge { display: inline-block; - background: #16A34A; color: #fff; font-size: 0.6875rem; font-weight: 700; @@ -2166,6 +2166,9 @@ hr { vertical-align: middle; } +.embassy-ready-badge { background: #16A34A; } +.embassy-processing-badge { background: #CA8A04; } + .btn-green { background: #16A34A; color: #fff; diff --git a/app/views/admin/embassy_applications/index.html.erb b/app/views/admin/embassy_applications/index.html.erb index 7ccdfc1..196f31d 100644 --- a/app/views/admin/embassy_applications/index.html.erb +++ b/app/views/admin/embassy_applications/index.html.erb @@ -77,7 +77,7 @@ <% if application.ready_at.present? %> Ready <% else %> - + Processing <% end %> diff --git a/app/views/plan/_plan_item.html.erb b/app/views/plan/_plan_item.html.erb index b2527e4..37c4700 100644 --- a/app/views/plan/_plan_item.html.erb +++ b/app/views/plan/_plan_item.html.erb @@ -62,7 +62,9 @@ <% when "submitted" %>

- Application filed · Serial <%= application.serial %> + Processing + Your application is being processed by the Embassy. Check this page for updates — we'll let you know once your passport is ready. + Serial <%= application.serial %>

<% if application.notary_profile.present? %> <%= render "embassy_applications/notary_callout", notary: application.notary_profile %> diff --git a/test/integration/plan_test.rb b/test/integration/plan_test.rb index 9733e8a..d503089 100644 --- a/test/integration/plan_test.rb +++ b/test/integration/plan_test.rb @@ -173,6 +173,32 @@ class PlanTest < ActionDispatch::IntegrationTest end end + test "/plan shows yellow Processing badge on embassy card while application is submitted but not yet ready" do + alice = users(:attendee_one) + passport_block = ScheduleItem.create!( + day: "thu", time_label: "9:00 AM", sort_time: 900, + title: "Passport Block", kind: :embassy, is_public: true, + offers_new_passport: true, new_passport_capacity: 4 + ) + plan_item = alice.plan_items.create!(schedule_item: passport_block) + booking = EmbassyBooking.create!( + user: alice, schedule_item: passport_block, plan_item: plan_item, + mode: "new_passport", state: "confirmed" + ) + application = EmbassyApplication.create!( + embassy_booking: booking, state: "submitted", submitted_at: Time.current, + drawn_question_ids: [], notary_profile_id: nil + ) + + sign_in_as alice + get plan_path + + assert_match "embassy-processing-badge", response.body + assert_match "being processed", response.body + assert_match application.serial, response.body + assert_no_match "embassy-ready-badge", response.body + end + test "/plan shows green READY badge on embassy card once application.ready_at is set" do alice = users(:attendee_one) passport_block = ScheduleItem.create!(