Skip to content

Commit cbf5895

Browse files
authored
Tailwind events crud (#248)
* Change permission requirements for events crud (all users can create new) * Update events crud with tailwind patterns * Update event specs
1 parent 24e7be4 commit cbf5895

File tree

15 files changed

+314
-189
lines changed

15 files changed

+314
-189
lines changed

app/controllers/event_registrations_controller.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ def bulk_create
5555
end
5656
end
5757

58+
def destroy
59+
@event_registration = EventRegistration.find(params[:id])
60+
if @event_registration
61+
@event_registration.destroy
62+
flash[:notice] = 'You are no longer registered.'
63+
redirect_to events_path
64+
else
65+
flash[:alert] = 'Unable to find that registration.'
66+
end
67+
end
68+
5869
private
5970

6071
def event_registration_params

app/controllers/events_controller.rb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
class EventsController < ApplicationController
22
before_action :set_event, only: %i[ show edit update destroy ]
3-
before_action :authorize_admin!, only: %i[ new edit update destroy ]
3+
before_action :authorize_admin!, only: %i[ edit update destroy ]
4+
5+
layout "tailwind", only: [:index, :edit, :new, :show]
46

57
def index
6-
@events = Event.all
8+
@events = Event.order(start_date: :desc)
9+
@events = @events.publicly_visible unless current_user.super_user?
710
end
811

912
def show
1013
end
1114

12-
def new
15+
def new # all logged in users can create events
1316
@event = Event.new
1417
end
1518

@@ -21,7 +24,7 @@ def create
2124

2225
respond_to do |format|
2326
if @event.save
24-
format.html { redirect_to @event, notice: "Event was successfully created." }
27+
format.html { redirect_to events_path, notice: "Event was successfully created." }
2528
format.json { render :show, status: :created, location: @event }
2629
else
2730
format.html { render :new, status: :unprocessable_entity }
@@ -33,7 +36,7 @@ def create
3336
def update
3437
respond_to do |format|
3538
if @event.update(event_params)
36-
format.html { redirect_to @event, notice: "Event was successfully updated." }
39+
format.html { redirect_to events_path, notice: "Event was successfully updated." }
3740
format.json { render :show, status: :ok, location: @event }
3841
else
3942
format.html { render :edit, status: :unprocessable_entity }
@@ -58,10 +61,11 @@ def set_event
5861
end
5962

6063
def event_params
61-
params.require(:event).permit(:title, :description, :start_date, :end_date, :registration_close_date, :publicly_visible)
64+
params.require(:event).permit(:title, :description, :start_date, :end_date,
65+
:registration_close_date, :publicly_visible)
6266
end
6367

6468
def authorize_admin!
65-
redirect_to events_path, alert: "You are not authorized to perform this action." unless current_admin
69+
redirect_to events_path, alert: "You are not authorized to perform this action." unless current_user.super_user?
6670
end
6771
end

app/models/event.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,11 @@ class Event < ApplicationRecord
33

44
validates_presence_of :title, :start_date, :end_date
55
validates_inclusion_of :publicly_visible, in: [true, false]
6+
7+
scope :publicly_visible, -> { where(publicly_visible: true) }
8+
9+
def registerable?
10+
publicly_visible &&
11+
(registration_close_date.nil? || registration_close_date >= Time.current)
12+
end
613
end

app/views/events/_form.html.erb

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,48 @@
1-
<%= form_for(@event) do |f| %>
2-
<%= render 'shared/errors', resource: @event if @event.errors.any? %>
1+
<%= simple_form_for(@event) do |f| %>
2+
<% if @event.errors.any? %>
3+
<%= render 'shared/errors', resource: @event %>
4+
<% end %>
35

4-
<div class="form-group">
5-
<%= f.label :title, class: 'bold' %>
6-
<%= f.text_field :title, class: 'form-control' %>
7-
8-
<%= f.label :description, class: 'bold' %>
9-
<%= f.text_area :description, class: 'form-control' %>
10-
11-
<%= f.label :start_date, class: 'bold' %>
12-
<%= f.text_field :start_date, type: 'datetime-local', class: 'form-control', value: @event.start_date&.strftime('%Y-%m-%dT%H:%M') %>
6+
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
7+
<div>
8+
<%= 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" } %>
9+
</div>
10+
<div>
11+
<%= 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" } %>
12+
</div>
13+
<div class="flex items-center mt-6 md:mt-0">
14+
<%= f.input :publicly_visible, as: :boolean, label: "Publicly visible?", wrapper_html: { class: "ml-2" } %>
15+
</div>
16+
</div>
1317

14-
<%= f.label :end_date, class: 'bold' %>
15-
<%= f.text_field :end_date, type: 'datetime-local', class: 'form-control', value: @event.end_date&.strftime('%Y-%m-%dT%H:%M') %>
18+
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
19+
<div>
20+
<%= f.label :start_date, "Start time", class: "block font-medium mb-1" %>
21+
<%= f.text_field :start_date, type: 'datetime-local',
22+
class: "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500",
23+
value: @event.start_date&.strftime('%Y-%m-%dT%H:%M') %>
24+
</div>
1625

17-
<%= f.label :registration_close_date, class: 'bold' %>
18-
<%= f.text_field :registration_close_date, type: 'datetime-local', class: 'form-control', value: @event.registration_close_date&.strftime('%Y-%m-%dT%H:%M') %>
26+
<div>
27+
<%= f.label :end_date, "End time", class: "block font-medium mb-1" %>
28+
<%= f.text_field :end_date, type: 'datetime-local',
29+
class: "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500",
30+
value: @event.end_date&.strftime('%Y-%m-%dT%H:%M') %>
31+
</div>
1932

20-
<div style="display: flex; align-items: center; margin-top: 10px; column-gap: 10px;">
21-
<%= f.label :publicly_visible, class: 'bold' %>
22-
<%= f.check_box :publicly_visible, { class: 'bold mt-0' }, '1', '0' %>
33+
<div>
34+
<%= f.label :registration_close_date, "Registration close time", class: "block font-medium mb-1" %>
35+
<%= f.text_field :registration_close_date, type: 'datetime-local',
36+
class: "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500",
37+
value: @event.registration_close_date&.strftime('%Y-%m-%dT%H:%M') %>
2338
</div>
2439
</div>
2540

26-
<div class="form-actions">
27-
<%= f.button :submit, class: 'btn btn-primary' %>
41+
<div class="flex justify-end gap-4">
42+
<%= link_to_button "Delete", @event, variant: :danger_outline, method: :delete, data: { confirm: "Are you sure?" } %>
43+
<%= link_to_button 'Cancel', events_path, variant: :secondary_outline %>
44+
<%= f.button :submit, class: 'inline-flex items-center gap-2 px-4 py-2 rounded-lg
45+
transition-colors duration-200 font-medium shadow-sm text-sm
46+
border border-blue-600 text-grey-600 hover:bg-blue-600 hover:text-white' %>
2847
</div>
2948
<% end %>

app/views/events/edit.html.erb

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
<h1>Editing Event</h1>
2-
3-
<%= render 'form', event: @event %>
4-
5-
<%= link_to 'Show', @event %> |
6-
<%= link_to 'Back', events_path %>
1+
<div class="w-full">
2+
<div class="flex items-start justify-between mb-6">
3+
<div class="pr-6">
4+
<h1 class="text-2xl font-semibold text-gray-900 mb-2">Edit Event</h1>
5+
<div class="border-b border-gray-300 mb-6"></div>
6+
<div class="mt-4 pt-4">
7+
<%= render 'form', event: @event %>
8+
</div>
9+
</div>
10+
<%= link_to_button 'View', event_path(@event), variant: :secondary_outline %>
11+
</div>
12+
</div>

app/views/events/index.html.erb

Lines changed: 116 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,128 @@
1-
<p id="notice"><%= notice %></p>
1+
<div class="w-full">
2+
<div class="flex items-start justify-between mb-6">
3+
<div class="pr-6">
4+
<h1 class="text-2xl font-semibold mb-2">Events</h1>
5+
Click 'Register' and then 'Register for Selected Events' to sign up for multiple events at once.
6+
</div>
7+
<%= link_to_button 'New Event', new_event_path, variant: :secondary_outline %>
8+
</div>
9+
</div>
210

3-
<h1>Events</h1>
11+
<!-- Divider -->
12+
<div class="w-full mt-6">
13+
<hr class="border-gray-300 mb-6">
14+
</div>
415

516
<%= form_with url: bulk_create_event_registrations_path, method: :post, local: true, id: 'bulk-registration-form' do |form| %>
6-
<table>
7-
<thead>
8-
<tr>
9-
<th></th>
10-
<th>Title</th>
11-
<th>Start Date</th>
12-
<th>End Date</th>
13-
<th>Actions</th>
14-
</tr>
15-
</thead>
16-
17-
<tbody>
17+
<% if @events.any? %>
18+
<% if @events.length > 10 %>
19+
<div class="flex justify-center mb-6">
20+
<%= form.submit "Register for Selected Events",
21+
class: "inline-flex items-center gap-2 px-4 py-2 rounded-lg
22+
transition-colors duration-200 font-medium shadow-sm text-sm
23+
border border-blue-600 bg-blue-600 text-white
24+
enabled:hover:bg-white enabled:hover:text-blue-600
25+
disabled:opacity-50 disabled:cursor-not-allowed",
26+
id: "register-button",
27+
disabled: true %>
28+
</div>
29+
<% end %>
30+
31+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
1832
<% @events.each do |event| %>
19-
<tr>
20-
<td style="padding-right: 10px;">
21-
<%= check_box_tag "event_ids[]", event.id, false, class: "event-checkbox" %>
22-
</td>
23-
<td style="padding-right: 10px;"><%= event.title %></td>
24-
<td style="padding-right: 10px;"><%= event.start_date.strftime("%B %d, %Y") if event.start_date %></td>
25-
<td style="padding-right: 10px;"><%= event.end_date.strftime("%B %d, %Y") if event.end_date %></td>
26-
<td style="padding-right: 10px;">
27-
<%= link_to 'Show', event %> |
28-
<%= link_to 'Edit', edit_event_path(event) %> |
29-
<%= link_to 'Destroy', event, method: :delete, data: { confirm: 'Are you sure?' } %>
30-
</td>
31-
</tr>
33+
<div class="relative bg-white border border-gray-200 rounded-xl shadow hover:shadow-md transition p-4 flex flex-col">
34+
<!-- Event Title -->
35+
<h3 class="text-lg font-semibold text-gray-900 mb-2">
36+
<%= link_to "#{ event.title}#{ " [HIDDEN]" if current_user.super_user? && !event.publicly_visible }",
37+
event_path(event),
38+
class: "text-gray-900 hover:underline font-medium block" %>
39+
40+
</h3>
41+
<% if event.event_registrations.where(email: current_user.email).exists? %>
42+
<span class="absolute top-2 right-2 text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full">
43+
Registered
44+
</span>
45+
<% end %>
46+
47+
<!-- Dates -->
48+
<p class="text-sm text-gray-600 mb-1">
49+
<span class="font-medium">Start:</span>
50+
<%= event.start_date&.strftime("%B %d, %Y") %>
51+
</p>
52+
<p class="text-sm text-gray-600 mb-3">
53+
<span class="font-medium">End:</span>
54+
<%= event.end_date&.strftime("%B %d, %Y") %>
55+
</p>
56+
57+
<div class="mt-3 flex gap-2">
58+
<!-- Admin Edit -->
59+
<% if current_user.super_user %>
60+
<%= link_to_button 'Edit', edit_event_path(event), variant: :secondary_outline %>
61+
<% end %>
62+
63+
<% if event.event_registrations.where(email: current_user.email).exists? %>
64+
<% registration = event.event_registrations.where(email: current_user.email).last %>
65+
<div class="mt-2 ml-auto">
66+
<%= link_to_button 'De-register',
67+
event_registration_path(registration),
68+
variant: :secondary_outline,
69+
method: :delete,
70+
data: { confirm: "Are you sure you want to de-register?" } %>
71+
</div>
72+
<% elsif event.registerable? %>
73+
<div class="mt-2 ml-auto">
74+
<label class="flex items-center gap-2 cursor-pointer">
75+
<%= check_box_tag "event_ids[]",
76+
event.id,
77+
false,
78+
id: "event_ids_#{event.id}",
79+
class: "event-checkbox h-5 w-5 text-blue-600 rounded border-gray-300 focus:ring-blue-500" %>
80+
<span class="text-gray-700">Register</span>
81+
</label>
82+
</div>
83+
<% else %>
84+
<div class="mt-2 ml-auto">
85+
<span class="text-sm text-gray-500 italic">Registration closed</span>
86+
</div>
87+
<% end %>
88+
</div>
89+
</div>
3290
<% end %>
33-
</tbody>
34-
</table>
91+
</div>
3592

36-
<br>
3793

38-
<div>
39-
<%= form.submit "Register for Selected Events", class: "btn btn-primary", id: "register-button", disabled: true %>
40-
<%= link_to 'New Event', new_event_path, class: "btn btn-primary" %>
41-
</div>
94+
<div class="flex justify-start m-6">
95+
<%= form.submit "Register for Selected Events",
96+
class: "inline-flex items-center gap-2 px-4 py-2 rounded-lg
97+
transition-colors duration-200 font-medium shadow-sm text-sm
98+
border border-blue-600 bg-blue-600 text-white
99+
enabled:hover:bg-white enabled:hover:text-blue-600
100+
disabled:opacity-50 disabled:cursor-not-allowed",
101+
id: "register-button",
102+
disabled: true %>
103+
</div>
104+
<% else %>
105+
<!-- Empty State -->
106+
<div class="text-center py-12 text-gray-500">
107+
No events available.
108+
</div>
109+
<% end %>
42110
<% end %>
43111

44112
<script>
45-
document.addEventListener('DOMContentLoaded', function() {
46-
const checkboxes = document.querySelectorAll('.event-checkbox');
47-
const registerButton = document.getElementById('register-button');
48-
49-
function updateRegisterButton() {
50-
const checkedBoxes = document.querySelectorAll('.event-checkbox:checked');
51-
registerButton.disabled = checkedBoxes.length === 0;
52-
}
53-
54-
checkboxes.forEach(checkbox => {
55-
checkbox.addEventListener('change', updateRegisterButton);
113+
document.addEventListener('DOMContentLoaded', function() {
114+
const checkboxes = document.querySelectorAll('.event-checkbox');
115+
const registerButton = document.getElementById('register-button');
116+
117+
function updateRegisterButton() {
118+
const checkedBoxes = document.querySelectorAll('.event-checkbox:checked');
119+
registerButton.disabled = checkedBoxes.length === 0;
120+
}
121+
122+
checkboxes.forEach(checkbox => {
123+
checkbox.addEventListener('change', updateRegisterButton);
124+
});
125+
126+
updateRegisterButton();
56127
});
57-
58-
updateRegisterButton();
59-
});
60128
</script>

app/views/events/new.html.erb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
<h1>New Event</h1>
1+
<div class="max-w-7xl mx-auto px-4 py-8">
2+
<h1 class="text-3xl font-semibold text-gray-900 mb-4">New Event</h1>
3+
<div class="border-b border-gray-300 mb-6"></div>
4+
<div class="mt-4 pt-4">
5+
<%= render 'form', event: @event %>
6+
</div>
7+
</div>
28

3-
<%= render 'form', event: @event %>
4-
5-
<%= link_to 'Back', events_path %>

0 commit comments

Comments
 (0)