diff --git a/app/controllers/event_registrations_controller.rb b/app/controllers/event_registrations_controller.rb index e1ef8f0fa..742b29eef 100644 --- a/app/controllers/event_registrations_controller.rb +++ b/app/controllers/event_registrations_controller.rb @@ -55,6 +55,17 @@ def bulk_create end end + def destroy + @event_registration = EventRegistration.find(params[:id]) + if @event_registration + @event_registration.destroy + flash[:notice] = 'You are no longer registered.' + redirect_to events_path + else + flash[:alert] = 'Unable to find that registration.' + end + end + private def event_registration_params diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index cef223c84..a0960662d 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -1,15 +1,18 @@ class EventsController < ApplicationController before_action :set_event, only: %i[ show edit update destroy ] - before_action :authorize_admin!, only: %i[ new edit update destroy ] + before_action :authorize_admin!, only: %i[ edit update destroy ] + + layout "tailwind", only: [:index, :edit, :new, :show] def index - @events = Event.all + @events = Event.order(start_date: :desc) + @events = @events.publicly_visible unless current_user.super_user? end def show end - def new + def new # all logged in users can create events @event = Event.new end @@ -21,7 +24,7 @@ def create respond_to do |format| if @event.save - format.html { redirect_to @event, notice: "Event was successfully created." } + format.html { redirect_to events_path, notice: "Event was successfully created." } format.json { render :show, status: :created, location: @event } else format.html { render :new, status: :unprocessable_entity } @@ -33,7 +36,7 @@ def create def update respond_to do |format| if @event.update(event_params) - format.html { redirect_to @event, notice: "Event was successfully updated." } + format.html { redirect_to events_path, notice: "Event was successfully updated." } format.json { render :show, status: :ok, location: @event } else format.html { render :edit, status: :unprocessable_entity } @@ -58,10 +61,11 @@ def set_event end def event_params - params.require(:event).permit(:title, :description, :start_date, :end_date, :registration_close_date, :publicly_visible) + params.require(:event).permit(:title, :description, :start_date, :end_date, + :registration_close_date, :publicly_visible) end def authorize_admin! - redirect_to events_path, alert: "You are not authorized to perform this action." unless current_admin + redirect_to events_path, alert: "You are not authorized to perform this action." unless current_user.super_user? end end diff --git a/app/models/event.rb b/app/models/event.rb index e3de81f83..920286f4b 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -3,4 +3,11 @@ class Event < ApplicationRecord validates_presence_of :title, :start_date, :end_date validates_inclusion_of :publicly_visible, in: [true, false] + + scope :publicly_visible, -> { where(publicly_visible: true) } + + def registerable? + publicly_visible && + (registration_close_date.nil? || registration_close_date >= Time.current) + end end diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb index 569e115a7..dc4d71033 100644 --- a/app/views/events/_form.html.erb +++ b/app/views/events/_form.html.erb @@ -1,29 +1,48 @@ -<%= form_for(@event) do |f| %> - <%= render 'shared/errors', resource: @event if @event.errors.any? %> +<%= simple_form_for(@event) do |f| %> + <% if @event.errors.any? %> + <%= render 'shared/errors', resource: @event %> + <% end %> -
- <%= f.label :title, class: 'bold' %> - <%= f.text_field :title, class: 'form-control' %> - - <%= f.label :description, class: 'bold' %> - <%= f.text_area :description, class: 'form-control' %> - - <%= f.label :start_date, class: 'bold' %> - <%= f.text_field :start_date, type: 'datetime-local', class: 'form-control', value: @event.start_date&.strftime('%Y-%m-%dT%H:%M') %> +
+
+ <%= f.input :title, label: "Event title", input_html: { class: "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500" } %> +
+
+ <%= f.input :description, input_html: { class: "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500" } %> +
+
+ <%= f.input :publicly_visible, as: :boolean, label: "Publicly visible?", wrapper_html: { class: "ml-2" } %> +
+
- <%= f.label :end_date, class: 'bold' %> - <%= f.text_field :end_date, type: 'datetime-local', class: 'form-control', value: @event.end_date&.strftime('%Y-%m-%dT%H:%M') %> +
+
+ <%= f.label :start_date, "Start time", class: "block font-medium mb-1" %> + <%= f.text_field :start_date, type: 'datetime-local', + class: "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", + value: @event.start_date&.strftime('%Y-%m-%dT%H:%M') %> +
- <%= f.label :registration_close_date, class: 'bold' %> - <%= f.text_field :registration_close_date, type: 'datetime-local', class: 'form-control', value: @event.registration_close_date&.strftime('%Y-%m-%dT%H:%M') %> +
+ <%= f.label :end_date, "End time", class: "block font-medium mb-1" %> + <%= f.text_field :end_date, type: 'datetime-local', + class: "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", + value: @event.end_date&.strftime('%Y-%m-%dT%H:%M') %> +
-
- <%= f.label :publicly_visible, class: 'bold' %> - <%= f.check_box :publicly_visible, { class: 'bold mt-0' }, '1', '0' %> +
+ <%= f.label :registration_close_date, "Registration close time", class: "block font-medium mb-1" %> + <%= f.text_field :registration_close_date, type: 'datetime-local', + class: "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", + value: @event.registration_close_date&.strftime('%Y-%m-%dT%H:%M') %>
-
- <%= f.button :submit, class: 'btn btn-primary' %> +
+ <%= link_to_button "Delete", @event, variant: :danger_outline, method: :delete, data: { confirm: "Are you sure?" } %> + <%= link_to_button 'Cancel', events_path, variant: :secondary_outline %> + <%= f.button :submit, class: 'inline-flex items-center gap-2 px-4 py-2 rounded-lg + transition-colors duration-200 font-medium shadow-sm text-sm + border border-blue-600 text-grey-600 hover:bg-blue-600 hover:text-white' %>
<% end %> diff --git a/app/views/events/edit.html.erb b/app/views/events/edit.html.erb index 6578b30d6..966aa40db 100644 --- a/app/views/events/edit.html.erb +++ b/app/views/events/edit.html.erb @@ -1,6 +1,12 @@ -

Editing Event

- -<%= render 'form', event: @event %> - -<%= link_to 'Show', @event %> | -<%= link_to 'Back', events_path %> +
+
+
+

Edit Event

+
+
+ <%= render 'form', event: @event %> +
+
+ <%= link_to_button 'View', event_path(@event), variant: :secondary_outline %> +
+
\ No newline at end of file diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index 4b36437e5..edcc23409 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -1,60 +1,128 @@ -

<%= notice %>

+
+
+
+

Events

+ Click 'Register' and then 'Register for Selected Events' to sign up for multiple events at once. +
+ <%= link_to_button 'New Event', new_event_path, variant: :secondary_outline %> +
+
-

Events

+ +
+
+
<%= form_with url: bulk_create_event_registrations_path, method: :post, local: true, id: 'bulk-registration-form' do |form| %> - - - - - - - - - - - - + <% if @events.any? %> + <% if @events.length > 10 %> +
+ <%= form.submit "Register for Selected Events", + class: "inline-flex items-center gap-2 px-4 py-2 rounded-lg + transition-colors duration-200 font-medium shadow-sm text-sm + border border-blue-600 bg-blue-600 text-white + enabled:hover:bg-white enabled:hover:text-blue-600 + disabled:opacity-50 disabled:cursor-not-allowed", + id: "register-button", + disabled: true %> +
+ <% end %> + +
<% @events.each do |event| %> -
- - - - - - +
+ +

+ <%= link_to "#{ event.title}#{ " [HIDDEN]" if current_user.super_user? && !event.publicly_visible }", + event_path(event), + class: "text-gray-900 hover:underline font-medium block" %> + +

+ <% if event.event_registrations.where(email: current_user.email).exists? %> + + Registered + + <% end %> + + +

+ Start: + <%= event.start_date&.strftime("%B %d, %Y") %> +

+

+ End: + <%= event.end_date&.strftime("%B %d, %Y") %> +

+ +
+ + <% if current_user.super_user %> + <%= link_to_button 'Edit', edit_event_path(event), variant: :secondary_outline %> + <% end %> + + <% if event.event_registrations.where(email: current_user.email).exists? %> + <% registration = event.event_registrations.where(email: current_user.email).last %> +
+ <%= link_to_button 'De-register', + event_registration_path(registration), + variant: :secondary_outline, + method: :delete, + data: { confirm: "Are you sure you want to de-register?" } %> +
+ <% elsif event.registerable? %> +
+ +
+ <% else %> +
+ Registration closed +
+ <% end %> +
+
<% end %> - -
TitleStart DateEnd DateActions
- <%= check_box_tag "event_ids[]", event.id, false, class: "event-checkbox" %> - <%= event.title %><%= event.start_date.strftime("%B %d, %Y") if event.start_date %><%= event.end_date.strftime("%B %d, %Y") if event.end_date %> - <%= link_to 'Show', event %> | - <%= link_to 'Edit', edit_event_path(event) %> | - <%= link_to 'Destroy', event, method: :delete, data: { confirm: 'Are you sure?' } %> -
+
-
-
- <%= form.submit "Register for Selected Events", class: "btn btn-primary", id: "register-button", disabled: true %> - <%= link_to 'New Event', new_event_path, class: "btn btn-primary" %> -
+
+ <%= form.submit "Register for Selected Events", + class: "inline-flex items-center gap-2 px-4 py-2 rounded-lg + transition-colors duration-200 font-medium shadow-sm text-sm + border border-blue-600 bg-blue-600 text-white + enabled:hover:bg-white enabled:hover:text-blue-600 + disabled:opacity-50 disabled:cursor-not-allowed", + id: "register-button", + disabled: true %> +
+ <% else %> + +
+ No events available. +
+ <% end %> <% end %> diff --git a/app/views/events/new.html.erb b/app/views/events/new.html.erb index 1feff5f26..a8f70b227 100644 --- a/app/views/events/new.html.erb +++ b/app/views/events/new.html.erb @@ -1,5 +1,8 @@ -

New Event

+
+

New Event

+
+
+ <%= render 'form', event: @event %> +
+
-<%= render 'form', event: @event %> - -<%= link_to 'Back', events_path %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 64aff136e..8b319f52c 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -1,36 +1,48 @@ -

<%= notice %>

- -

Event Details

- -

- Title: - <%= @event.title %> -

- -

- Description: - <%= @event.description %> -

- -

- Start Date: - <%= @event.start_date.strftime("%B %d, %Y %I:%M %p") if @event.start_date.present? %> -

- -

- End Date: - <%= @event.end_date.strftime("%B %d, %Y %I:%M %p") if @event.end_date.present? %> -

- -

- Registration Close Date: - <%= @event.registration_close_date.strftime("%B %d, %Y %I:%M %p") if @event.registration_close_date.present? %> -

- -

- Publicly Visible: - <%= @event.publicly_visible? ? 'Yes' : 'No' %> -

- -<%= link_to 'Edit', edit_event_path(@event) %> | -<%= link_to 'Back', events_path %> +
+
+ +
+ <%= link_to_button("Index", events_path, variant: :secondary_outline) %> + <% if current_user.super_user? %> + <%= link_to_button("Edit", edit_event_path(@event), variant: :primary_outline) %> + <% end %> +
+
+

Event Details

+ +
+
+

Title:

+

<%= @event.title.presence || "—" %>

+
+ +
+

Publicly Visible:

+

<%= @event.publicly_visible? ? "Yes" : "No" %>

+
+
+ +
+

Description:

+

<%= @event.description.presence || "—" %>

+
+ +
+
+

Start Date:

+

<%= @event.start_date&.strftime("%B %d, %Y %l:%M %p") || "—" %>

+
+ +
+

End Date:

+

<%= @event.end_date&.strftime("%B %d, %Y %l:%M %p") || "—" %>

+
+ +
+

Registration Close Date:

+

<%= @event.registration_close_date&.strftime("%B %d, %Y %l:%M %p") || "—" %>

+
+
+
+
+
diff --git a/config/routes.rb b/config/routes.rb index afc3bc368..0ebf0acaa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -40,7 +40,7 @@ resources :workshop_logs resources :events - resources :event_registrations, only: [:create] do + resources :event_registrations, only: [:create, :destroy] do collection do post :bulk_create end diff --git a/spec/requests/events_spec.rb b/spec/requests/events_spec.rb index 749f01b39..4fb60a5a4 100644 --- a/spec/requests/events_spec.rb +++ b/spec/requests/events_spec.rb @@ -28,14 +28,15 @@ } } let(:user) { create(:user) } - let(:admin) { create(:admin) } + let(:admin) { create(:user, super_user: true) } let(:event) { Event.create!(valid_attributes) } describe "GET /index" do it "renders a successful response" do sign_in user get events_url - + puts "=====Status: #{response.status}" + puts "Error snippet: #{response.body.scan(/(.*?)<\/pre>/m)}" expect(response).to be_successful end end @@ -67,7 +68,7 @@ describe "GET /edit" do describe 'when signed in as an admin' do it "renders a successful response" do - sign_in user + sign_in admin allow_any_instance_of(ApplicationController). to receive(:current_admin).and_return(true) @@ -98,10 +99,25 @@ }.to change(Event, :count).by(1) end - it "redirects to the created event" do + it "redirects to the events index" do sign_in user post events_url, params: { event: valid_attributes } - expect(response).to redirect_to(event_url(Event.last)) + expect(response).to redirect_to(events_url) + end + + it "displays notice if present" do + sign_in user + post events_url, params: { event: { + title: "sample title", + description: "sample description", + start_date: 1.day.from_now, + end_date: 2.days.from_now, + registration_close_date: 3.days.ago, + publicly_visible: true + } } + follow_redirect! # flash shows after redirect + + expect(response.body).to include("Event was successfully created") end end @@ -176,7 +192,7 @@ describe "DELETE /destroy" do it "destroys the requested event" do event = Event.create!(valid_attributes) - sign_in user + sign_in admin allow_any_instance_of(ApplicationController). to receive(:current_admin).and_return(true) expect { diff --git a/spec/views/events/_form.html.erb_spec.rb b/spec/views/events/_form.html.erb_spec.rb index b5c99f210..90d45f338 100644 --- a/spec/views/events/_form.html.erb_spec.rb +++ b/spec/views/events/_form.html.erb_spec.rb @@ -22,28 +22,18 @@ it "renders all form labels" do render - expect(rendered).to have_selector("label", text: "Title") + expect(rendered).to have_selector("label", text: "Event title") expect(rendered).to have_selector("label", text: "Description") - expect(rendered).to have_selector("label", text: "Start date") - expect(rendered).to have_selector("label", text: "End date") - expect(rendered).to have_selector("label", text: "Registration close date") + expect(rendered).to have_selector("label", text: "Start time") + expect(rendered).to have_selector("label", text: "End time") + expect(rendered).to have_selector("label", text: "Registration close time") expect(rendered).to have_selector("label", text: "Publicly visible") end - it "applies correct CSS classes" do - render - - expect(rendered).to have_selector(".form-group") - expect(rendered).to have_selector("input.form-control") - expect(rendered).to have_selector("textarea.form-control") - expect(rendered).to have_selector("label.bold") - end - it "renders submit button" do render - expect(rendered).to have_selector("button[type='submit']") - expect(rendered).to have_selector(".btn.btn-primary") + expect(rendered).to have_selector("input[type='submit']") end context "when event has existing data" do diff --git a/spec/views/events/edit.html.erb_spec.rb b/spec/views/events/edit.html.erb_spec.rb index b3920dc09..98531cd66 100644 --- a/spec/views/events/edit.html.erb_spec.rb +++ b/spec/views/events/edit.html.erb_spec.rb @@ -18,7 +18,7 @@ it "renders the editing event heading" do render - expect(rendered).to have_selector("h1", text: "Editing Event") + expect(rendered).to have_selector("h1", text: "Edit Event") end it "renders the form partial with event data" do @@ -33,14 +33,13 @@ it "renders action links" do render - expect(rendered).to have_link("Show", href: event_path(event)) - expect(rendered).to have_link("Back", href: events_path) + expect(rendered).to have_link("View", href: event_path(event)) end it "renders submit button" do render - expect(rendered).to have_selector("button[type='submit']") + expect(rendered).to have_selector("input[type='submit']") end context "when event has errors" do diff --git a/spec/views/events/index.html.erb_spec.rb b/spec/views/events/index.html.erb_spec.rb index 6dd9edad2..fe714658b 100644 --- a/spec/views/events/index.html.erb_spec.rb +++ b/spec/views/events/index.html.erb_spec.rb @@ -7,44 +7,48 @@ create(:permission, :combined) end - let(:user) { create(:user) } - let(:event1) { create(:event, title: "Event 1", start_date: 1.day.from_now, end_date: 2.days.from_now) } - let(:event2) { create(:event, title: "Event 2", start_date: 3.days.from_now, end_date: 4.days.from_now) } - let(:events) { [event1, event2] } + let(:user) { create(:user, super_user: true) } + let(:event_closed) { create(:event, title: "Event 1", + start_date: 1.day.from_now, end_date: 2.days.from_now, + publicly_visible: true, + registration_close_date: -3.days.from_now) } + let(:event_open) { create(:event, title: "Event 2", + start_date: 3.days.from_now, end_date: 4.days.from_now, + registration_close_date: 5.days.from_now, + publicly_visible: true) } + let(:event_open_2) { create(:event, title: "Event 2", + start_date: 3.days.from_now, end_date: 4.days.from_now, + registration_close_date: nil, + publicly_visible: true) } + let(:events) { [event_open, event_open] } before do assign(:events, events) allow(view).to receive(:current_user).and_return(user) end - it "renders the events table with proper headers" do - render - - expect(rendered).to have_selector("th", text: "") - expect(rendered).to have_selector("th", text: "Title") - expect(rendered).to have_selector("th", text: "Start Date") - expect(rendered).to have_selector("th", text: "End Date") - expect(rendered).to have_selector("th", text: "Actions") - end - it "renders each event with checkbox and details" do + assign(:events, [event_open, event_open_2, event_closed]) render events.each do |event| - expect(rendered).to have_selector("input[type='checkbox'][value='#{event.id}']") expect(rendered).to have_content(event.title) expect(rendered).to have_content(event.start_date.strftime("%B %d, %Y")) expect(rendered).to have_content(event.end_date.strftime("%B %d, %Y")) end + + # Only the open event should have a checkbox + expect(rendered).to have_selector("input[type='checkbox'][id='event_ids_#{event_open.id}']") + expect(rendered).to have_selector("input[type='checkbox'][id='event_ids_#{event_open_2.id}']") + expect(rendered).not_to have_selector("input[type='checkbox'][id='event_ids_#{event_closed.id}']") end it "renders action links for each event" do render events.each do |event| - expect(rendered).to have_link("Show", href: event_path(event)) + expect(rendered).to have_link(event.title, href: event_path(event)) expect(rendered).to have_link("Edit", href: edit_event_path(event)) - expect(rendered).to have_link("Destroy", href: event_path(event)) end end @@ -70,22 +74,13 @@ expect(rendered).to have_content("addEventListener") end - it "displays notice if present" do - flash[:notice] = "Test notice" - render - - expect(rendered).to have_selector("p#notice", text: "Test notice") - end - context "when no events exist" do let(:events) { [] } - it "renders empty table" do + it "renders empty message" do render - expect(rendered).to have_selector("table") - expect(rendered).to have_selector("th", text: "Title") - expect(rendered).not_to have_selector("input[type='checkbox']") + expect(rendered).to have_content("No events available") end end diff --git a/spec/views/events/new.html.erb_spec.rb b/spec/views/events/new.html.erb_spec.rb index a681c7c28..f44842b6c 100644 --- a/spec/views/events/new.html.erb_spec.rb +++ b/spec/views/events/new.html.erb_spec.rb @@ -25,16 +25,16 @@ expect(rendered).to have_selector("input[type='checkbox'][name='event[publicly_visible]']") end - it "renders the Back link" do + it "renders the Cancel link" do render - expect(rendered).to have_link("Back", href: events_path) + expect(rendered).to have_link("Cancel", href: events_path) end it "renders submit button" do render - expect(rendered).to have_selector("button[type='submit']") + expect(rendered).to have_selector("input[type='submit']") end context "when event has errors" do @@ -48,7 +48,7 @@ it "renders error messages" do render - expect(rendered).to have_selector(".form-group") + expect(rendered).to have_selector(".errors") end end end \ No newline at end of file diff --git a/spec/views/events/show.html.erb_spec.rb b/spec/views/events/show.html.erb_spec.rb index cb991ee21..9fa907a6f 100644 --- a/spec/views/events/show.html.erb_spec.rb +++ b/spec/views/events/show.html.erb_spec.rb @@ -12,6 +12,7 @@ before do assign(:event, event) + allow(view).to receive(:current_user).and_return(build_stubbed(:user, super_user: true)) end it "renders the event title" do @@ -34,7 +35,7 @@ expect(rendered).to have_content("January 15, 2024 10:00 AM") expect(rendered).to have_content("End Date:") - expect(rendered).to have_content("January 15, 2024 04:00 PM") + expect(rendered).to have_content("January 15, 2024 4:00 PM") expect(rendered).to have_content("Registration Close Date:") expect(rendered).to have_content("January 10, 2024 11:59 PM") @@ -44,14 +45,7 @@ render expect(rendered).to have_link("Edit", href: edit_event_path(event)) - expect(rendered).to have_link("Back", href: events_path) - end - - it "displays notice if present" do - flash[:notice] = "Event created successfully" - render - - expect(rendered).to have_selector("p#notice", text: "Event created successfully") + expect(rendered).to have_link("Index", href: events_path) end context "when event has minimal data" do @@ -67,7 +61,8 @@ expect(rendered).to have_content("Minimal Event") expect(rendered).to have_content("Event with minimal data") - expect(rendered).to have_content(event.start_date.strftime("%B %d, %Y %I:%M %p")) + expect(rendered).to include(event.start_date.strftime("%B %d, %Y")) # "October 02, 2025" + expect(rendered).to include(event.start_date.strftime("%-l:%M %p")) # "5:43 PM" end end