From e65d553781dc6724fd0f27c444948fe18dbf2e66 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Wed, 25 Feb 2026 08:01:10 +0000 Subject: [PATCH] feat: split /events into upcoming and past pages - /events now redirects to /events/upcoming - /events/upcoming shows upcoming events (paginated at 20) - /events/past shows past events (paginated at 20, sorted descending) - Updated navigation with Upcoming Events and Past Events items - Updated footer and dashboard links to point to upcoming_events_path - Renamed index.html.haml to upcoming.html.haml - Updated tests for new behavior --- Gemfile.lock | 1 + app/controllers/events_controller.rb | 56 +++++++++++++++++-------- app/views/dashboard/show.html.haml | 2 +- app/views/events/index.html.haml | 15 ------- app/views/events/past.html.haml | 13 ++++++ app/views/events/upcoming.html.haml | 13 ++++++ app/views/layouts/_footer.html.haml | 2 +- app/views/layouts/_navigation.html.haml | 3 +- config/routes.rb | 6 ++- spec/features/listing_events_spec.rb | 56 +++++++++++++------------ 10 files changed, 104 insertions(+), 63 deletions(-) delete mode 100644 app/views/events/index.html.haml create mode 100644 app/views/events/past.html.haml create mode 100644 app/views/events/upcoming.html.haml diff --git a/Gemfile.lock b/Gemfile.lock index fa586224d..a45491062 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -604,6 +604,7 @@ GEM PLATFORMS aarch64-linux arm64-darwin-20 + arm64-darwin-23 arm64-darwin-25 x86_64-darwin-24 x86_64-linux diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 8b3007984..8baa7ba08 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -3,28 +3,20 @@ class EventsController < ApplicationController before_action :is_logged_in?, only: %i[student coach] - RECENT_EVENTS_DISPLAY_LIMIT = 40 - def index + redirect_to upcoming_events_path + end + + def upcoming fresh_when(latest_model_updated, etag: latest_model_updated) - events = [Workshop.past.includes(:chapter, - :sponsors).joins(:chapter).merge(Chapter.active).limit(RECENT_EVENTS_DISPLAY_LIMIT)] - events << Meeting.past.includes(:venue).limit(RECENT_EVENTS_DISPLAY_LIMIT) - events << Event.past.includes(:venue, :sponsors, :sponsorships).limit(RECENT_EVENTS_DISPLAY_LIMIT) - events = events.compact.flatten.sort_by(&:date_and_time).reverse.first(RECENT_EVENTS_DISPLAY_LIMIT) - events_hash_grouped_by_date = events.group_by(&:date) - @past_events = events_hash_grouped_by_date.map.each_with_object({}) do |(key, value), hash| - hash[key] = EventPresenter.decorate_collection(value) - end + @events, @pagy = fetch_upcoming_events + end - events = [Workshop.includes(:chapter, :sponsors).upcoming.joins(:chapter).merge(Chapter.active)] - events << Meeting.upcoming.all - events << Event.upcoming.includes(:venue, :sponsors, :sponsorships).all - events = events.compact.flatten.sort_by(&:date_and_time).group_by(&:date) - @events = events.map.each_with_object({}) do |(key, value), hash| - hash[key] = EventPresenter.decorate_collection(value) - end + def past + fresh_when(latest_model_updated, etag: latest_model_updated) + + @past_events, @pagy = fetch_past_events end def show @@ -78,4 +70,32 @@ def find_invitation_and_redirect_to_event(role) def set_event @event = Event.find_by(slug: params[:event_id]) end + + def fetch_upcoming_events + events = [Workshop.includes(:chapter, :sponsors).upcoming.joins(:chapter).merge(Chapter.active)] + events << Meeting.upcoming.all + events << Event.upcoming.includes(:venue, :sponsors, :sponsorships).all + + sorted = events.compact.flatten.sort_by(&:date_and_time) + pagy, paginated = pagy(sorted, items: 20) + + grouped = paginated.group_by(&:date) + decorated = grouped.transform_values { |items| EventPresenter.decorate_collection(items) } + + [decorated, pagy] + end + + def fetch_past_events + events = [Workshop.past.includes(:chapter, :sponsors).joins(:chapter).merge(Chapter.active)] + events << Meeting.past.all + events << Event.past.includes(:venue, :sponsors, :sponsorships).all + + sorted = events.compact.flatten.sort_by(&:date_and_time).reverse + pagy, paginated = pagy(sorted, items: 20) + + grouped = paginated.group_by(&:date) + decorated = grouped.transform_values { |items| EventPresenter.decorate_collection(items) } + + [decorated, pagy] + end end diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index f4f95060b..a4c19c2a1 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -74,7 +74,7 @@ = render workshops - if @has_more_events - = link_to 'Explore all events →', events_path, class: 'btn btn-outline-primary mt-3' + = link_to 'Explore all events →', upcoming_events_path, class: 'btn btn-outline-primary mt-3' .col-lg-4.pl-lg-5 %h3 diff --git a/app/views/events/index.html.haml b/app/views/events/index.html.haml deleted file mode 100644 index fd5908a8d..000000000 --- a/app/views/events/index.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -- title t('events.title') - -.container{'data-test': 'upcoming-events'} - .row - .col - - if @events.any? - %h3.mb-4 Upcoming Events - = render partial: 'events', locals: { grouped_events: @events } - -.container{'data-test': 'past-events'} - .row - .col - - if @past_events.any? - %h3.mb-4 Past Events - = render partial: 'events', locals: { grouped_events: @past_events } diff --git a/app/views/events/past.html.haml b/app/views/events/past.html.haml new file mode 100644 index 000000000..a5398bcfa --- /dev/null +++ b/app/views/events/past.html.haml @@ -0,0 +1,13 @@ +- title 'Past Events' + +.container{'data-test': 'past-events'} + .row + .col + - if @past_events.any? + %h3.mb-4 Past Events + + = render partial: 'events', locals: { grouped_events: @past_events } + +- if @pagy + .container.mt-4 + = render partial: 'shared/pagination', locals: { pagy: @pagy, model: 'event' } diff --git a/app/views/events/upcoming.html.haml b/app/views/events/upcoming.html.haml new file mode 100644 index 000000000..968937d28 --- /dev/null +++ b/app/views/events/upcoming.html.haml @@ -0,0 +1,13 @@ +- title t('events.title') + +.container{'data-test': 'upcoming-events'} + .row + .col + - if @events.any? + %h3.mb-4 Upcoming Events + + = render partial: 'events', locals: { grouped_events: @events } + +- if @pagy + .container.mt-4 + = render partial: 'shared/pagination', locals: { pagy: @pagy, model: 'event' } diff --git a/app/views/layouts/_footer.html.haml b/app/views/layouts/_footer.html.haml index 528db6f5b..201ede017 100644 --- a/app/views/layouts/_footer.html.haml +++ b/app/views/layouts/_footer.html.haml @@ -27,7 +27,7 @@ %li= link_to t("navigation.codebar_stories"), "https://medium.com/codebar-stories" %li= link_to t("navigation.coaches"), coaches_path %li= link_to t("navigation.sponsors"), sponsors_path - %li= link_to t("navigation.events"), events_path + %li= link_to t("navigation.events"), upcoming_events_path %li= link_to t("navigation.jobs"), "https://jobs.codebar.io/" %li.active= link_to t("navigation.donate"), new_donation_path %li= link_to "Buy us a coffee", "https://buymeacoffee.com/codebarhq", target: '_blank', rel: 'noopener noreferrer' diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index 8edc2ab52..089a5861f 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -10,7 +10,8 @@ = link_to '#', {'role': 'button', 'aria-expanded': 'false', 'data-bs-toggle': 'dropdown', class: 'nav-link border-0 dropdown-toggle', id: 'navbarDropdownMenuLinkEvents'} do Events %ul.dropdown-menu{'aria-labelledby': 'navbarDropdownMenuLinkEvents'} - %li= link_to 'Events', events_path, class: 'dropdown-item' + %li= link_to 'Upcoming Events', upcoming_events_path, class: 'dropdown-item' + %li= link_to 'Past Events', past_events_path, class: 'dropdown-item' %li= link_to 'codebar Festival', 'http://festival.codebar.io/', class: 'dropdown-item' %li= link_to 'uncodebar', 'https://uncodebar.com', class: 'dropdown-item' %li.nav-item.dropdown diff --git a/config/routes.rb b/config/routes.rb index fc6dc3893..ca944c783 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -32,7 +32,7 @@ get 'unsubscribe/:token' => 'members#unsubscribe', as: :unsubscribe - resources :invitation, only: [:show, :update] do + resources :invitation, only: %i[show update] do member do post 'accept' get 'accept' @@ -50,6 +50,10 @@ end resources :events, only: %i[index show] do + collection do + get :upcoming + get :past + end post 'rsvp' get 'student', as: :student_rsvp get 'coach', as: :coach_rsvp diff --git a/spec/features/listing_events_spec.rb b/spec/features/listing_events_spec.rb index b966a271a..b736a43f2 100644 --- a/spec/features/listing_events_spec.rb +++ b/spec/features/listing_events_spec.rb @@ -1,40 +1,44 @@ RSpec.feature 'event listing', type: :feature do - describe 'I can see the names and titles of events' do - let!(:upcoming_workshop) { Fabricate(:workshop) } + describe 'I can see upcoming events' do + let!(:chapter) { Fabricate(:chapter, active: true) } + let!(:upcoming_workshop) { Fabricate(:workshop, chapter: chapter) } let!(:event) { Fabricate(:event) } - let!(:past_event) { Fabricate(:event, date_and_time: Time.zone.now - 2.weeks) } - let!(:past_workshop) { Fabricate(:workshop, date_and_time: Time.zone.now - 1.week) } - before do - visit events_path + scenario 'displays upcoming events page' do + visit upcoming_events_path + expect(page).to have_content 'Upcoming Events' + expect(page).to have_content event.name end + end + + describe 'I can see past events' do + let!(:chapter) { Fabricate(:chapter, active: true) } + let!(:past_event) { Fabricate(:event, date_and_time: Time.zone.now - 2.weeks) } + let!(:past_workshop) { Fabricate(:workshop, date_and_time: Time.zone.now - 1.week, chapter: chapter) } - scenario 'i can view a list with upcoming events' do - within('*[data-test=upcoming-events]') do - expect(page).to have_content 'Workshop' - expect(page).to have_content event.name - end + scenario 'displays past events page' do + visit past_events_path + expect(page).to have_content 'Past Events' + expect(page).to have_content past_event.name end + end - scenario 'i can view a list with past events' do - within('*[data-test=past-events]') do - expect(page).to have_content 'Workshop' - expect(page).to have_content past_event.name - end + describe 'root /events redirects to /events/upcoming' do + scenario 'redirects to upcoming events' do + visit events_path + expect(page).to have_content 'Upcoming Events' + expect(page).to have_current_path '/events/upcoming', ignore_query: true end end - context 'when there are more than the specified number of past events' do - scenario 'I can only as many events allowed by the display limits' do - Fabricate.times(2, :event, date_and_time: 2.weeks.ago) - stub_const('EventsController::RECENT_EVENTS_DISPLAY_LIMIT', 2) - Fabricate(:workshop, date_and_time: 3.weeks.ago) + context 'pagination' do + scenario 'past events paginates at 20 per page' do + chapter = Fabricate(:chapter, active: true) + Fabricate.times(22, :event, date_and_time: 2.weeks.ago) + Fabricate(:workshop, date_and_time: 3.weeks.ago, chapter: chapter) - visit events_path - within('*[data-test=past-events]') do - expect(page).to have_selector('*[data-test=event]', count: 2) - expect(page).not_to have_content 'Workshop' - end + visit past_events_path + expect(page).to have_selector('.card', count: 20) end end end