Skip to content
7 changes: 7 additions & 0 deletions config/i18n-tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,13 @@ ignore_unused:
- decidim.meetings.meetings.show.withdraw_btn_hint
- decidim.meetings.meetings.show.withdraw_confirmation_html
- decidim.meetings.meetings.show.withdraw_meeting
- decidim.meetings.meetings.show.cancel_waitlist
- decidim.meetings.meetings.show.join_waitlist
- decidim.meetings.meetings.show.leave
- decidim.meetings.meetings.show.registration_confirmation
- decidim.meetings.meetings.show.registration_title
- decidim.meetings.meetings.show.waitlist_confirmation
- decidim.meetings.meetings.show.waitlist_title
- decidim.proposals.proposals.show.withdraw_btn_hint
- decidim.proposals.proposals.show.withdraw_confirmation_html
- decidim.proposals.proposals.show.withdraw_proposal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@
<%= decidim_form_for(registration_form, url: meeting_registration_path(model), method: :delete) do |form| %>
<div data-dialog-container>
<%= icon "door-open-line" %>
<h2 id="dialog-title-meeting-cancelation-confirm-<%= model.id %>" data-dialog-title><%= t("leave", scope: "decidim.meetings.meetings.show") %></h2>
<h2 id="dialog-title-meeting-cancelation-confirm-<%= model.id %>" data-dialog-title>
<%= i18n_modal_title %>
</h2>
<div>

<div id="dialog-desc-meeting-cancelation-confirm-<%= model.id %>" class="meeting__cancelation-modal__description"><%= t("leave_confirmation", scope: "decidim.meetings.meetings.show") %></div>
<div id="dialog-desc-meeting-cancelation-confirm-<%= model.id %>" class="meeting__cancelation-modal__description">
<%= i18n_modal_confirmation_text %>
</div>
</div>
</div>
<div data-dialog-actions>
<button type="button" class="button button__sm md:button__lg button__transparent-secondary" data-dialog-close="meeting-cancelation-confirm-<%= model.id %>">
<span><%= t("close", scope: "decidim.shared.flag_modal") %></span>
</button>
<button type="submit" class="button button__sm md:button__lg button__transparent-secondary">
<span><%= t("leave", scope: "decidim.meetings.meetings.show") %></span>
<span><%= cancel_button_text %></span>
<%= icon "arrow-right-line" %>
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<%= waiting_list_info %>
<% unless options[:hide_modal] %>
<%= render :cancelation_modal %>
<% end %>
Expand All @@ -7,6 +8,6 @@
class: button_classes,
data: { "dialog-open": "meeting-cancelation-confirm-#{model.id}" }
) do %>
<%= t("leave", scope: "decidim.meetings.meetings.show") %>
<%= cancel_button_text %>
<%= icon icon_name %>
<% end %>
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,48 @@ def show
render
end

def waiting_list_info
cell("decidim/announcement", t("waitlist_info", scope: "decidim.meetings.meetings.show")) if registration_status == "on_waiting_list"
end

private

def current_component
model.component
end

def registration_status
model.registrations.find_by(user: current_user)&.status
end

def action_keys
if registration_status == "on_waiting_list"
{
button: "cancel_waitlist",
modal_title: "waitlist_title",
modal_confirmation: "waitlist_confirmation"
}
else
{
button: "leave",
modal_title: "registration_title",
modal_confirmation: "registration_confirmation"
}
end
end

def cancel_button_text
I18n.t(action_keys[:button], scope: "decidim.meetings.meetings.show")
end

def i18n_modal_title
I18n.t(action_keys[:modal_title], scope: "decidim.meetings.meetings.show")
end

def i18n_modal_confirmation_text
I18n.t(action_keys[:modal_confirmation], scope: "decidim.meetings.meetings.show")
end

def button_classes
"button button__sm button__transparent-secondary w-full"
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<%= decidim_modal id: "meeting-registration-confirm-#{model.id}", class: "meeting__registration-modal" do %>
<%= decidim_form_for(registration_form, url: meeting_registration_path(model), method: :post) do |form| %>
<% action = model.has_available_slots? ? "registration" : "waitlist" %>
<%= decidim_modal id: "meeting-#{action}-confirm-#{model.id}", class: "meeting__registration-modal" do %>
<%= decidim_form_for(registration_form, url: model.has_available_slots? ? meeting_registration_path(model) : join_waitlist_meeting_registration_path(model), method: :post) do |form| %>
<div data-dialog-container>
<%= icon "login-circle-line" %>
<h2 id="dialog-title-meeting-registration-confirm-<%= model.id %>" data-dialog-title><%= t("join", scope: "decidim.meetings.meetings.show") %></h2>
<h2 id="dialog-title-meeting-registration-confirm-<%= model.id %>" data-dialog-title><%= t(model.has_available_slots? ? "join" : "join_waitlist", scope: "decidim.meetings.meetings.show") %></h2>
<div>

<div class="form__wrapper">
Expand All @@ -13,7 +14,7 @@
</div>
</div>
<div data-dialog-actions>
<button type="button" class="button button__sm md:button__lg button__transparent-secondary" data-dialog-close="meeting-registration-confirm-<%= model.id %>">
<button type="button" class="button button__sm md:button__lg button__transparent-secondary" data-dialog-close="meeting-<%= action %>-confirm-<%= model.id %>">
<span><%= i18n_cancel_text %></span>
</button>
<button type="submit" class="button button__sm md:button__lg button__secondary">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<% if model.registration_form_enabled? %>
<%= action_authorized_link_to(
:join_waitlist,
join_waitlist_meeting_registration_path(model),
class: button_classes
) do %>
<%= i18n_join_waitlist_text %>
<%= icon(icon_name) %>
<% end %>
<% else %>
<%= action_authorized_button_to(
:join_waitlist,
"#",
class: button_classes,
data: { "dialog-open": current_user.present? ? "meeting-waitlist-confirm-#{model.id}" : "loginModal" },
resource: model
) do %>
<%= i18n_join_waitlist_text %>
<%= icon(icon_name) %>
<% end %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

module Decidim
module Meetings
# This cell renders the button to join a waitlist.
class JoinWaitlistButtonCell < Decidim::ViewModel
include MeetingCellsHelper

def show
return unless model.waitlist_enabled? && !model.has_available_slots? && model.can_be_joined_by?(current_user)
return if model.has_registration_for?(current_user)

render
end

private

def current_component
model.component
end

def button_classes
"button button__sm button__transparent-secondary w-full"
end

def i18n_join_waitlist_text
return if !model.waitlist_enabled? && model.has_available_slots?

I18n.t("add_to_waitlist", scope: "decidim.meetings.meetings.show")
end

def icon_name
"clockwise-line"
end

def registration_form
@registration_form ||= Decidim::Meetings::JoinMeetingForm.new
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def fields_from_meeting
available_slots: meeting.available_slots,
registration_terms: meeting.registration_terms,
reserved_slots: meeting.reserved_slots,
waitlist_enabled: meeting.waitlist_enabled,
customize_registration_email: meeting.customize_registration_email,
registration_form_enabled: meeting.registration_form_enabled,
registration_email_custom_content: meeting.registration_email_custom_content
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def attributes
available_slots: form.available_slots,
reserved_slots: form.reserved_slots,
registration_terms: form.registration_terms,
waitlist_enabled: form.waitlist_enabled,
customize_registration_email: form.customize_registration_email
}
extra_params.merge!(registration_email_custom_content: form.registration_email_custom_content) if form.customize_registration_email
Expand Down
57 changes: 57 additions & 0 deletions decidim-meetings/app/commands/decidim/meetings/join_waitlist.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module Decidim
module Meetings
# This command is executed when the user joins a meeting waitlist.
class JoinWaitlist < Decidim::Command
delegate :current_user, to: :form

def initialize(meeting, form)
@meeting = meeting
@user_group = Decidim::UserGroup.find_by(id: form.user_group_id)
@form = form
end

def call
return broadcast(:invalid) unless can_join_waitlist?
return broadcast(:invalid_form) unless form.valid?

meeting.with_lock do
create_waitlist_entry
send_waitlist_notification
end

broadcast(:ok)
end

private

attr_reader :meeting, :user_group, :form

def can_join_waitlist?
meeting.waitlist_enabled? &&
!meeting.registrations.exists?(user: current_user) &&
!meeting.has_available_slots?
end

def create_waitlist_entry
@registration = Decidim::Meetings::Registration.create!(
meeting:,
user: current_user,
user_group:,
public_participation: form.public_participation,
status: :on_waiting_list
)
end

def send_waitlist_notification
Decidim::EventsManager.publish(
event: "decidim.events.meetings.meeting_waitlist_added",
event_class: Decidim::Meetings::MeetingRegistrationNotificationEvent,
resource: meeting,
affected_users: [current_user]
)
end
end
end
end
25 changes: 25 additions & 0 deletions decidim-meetings/app/commands/decidim/meetings/leave_meeting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def call
destroy_registration
destroy_questionnaire_answers
decrement_score
move_from_waitlist!
end
broadcast(:ok)
end
Expand All @@ -50,6 +51,30 @@ def destroy_questionnaire_answers
def decrement_score
Decidim::Gamification.decrement_score(@user, :attended_meetings)
end

def send_email_confirmation(registration, user, meeting)
Decidim::Meetings::RegistrationMailer.confirmation(user, meeting, registration).deliver_later
end

def move_from_waitlist!
return unless @meeting.remaining_slots.positive?

on_waiting_list_user = @meeting.registrations.on_waiting_list.order(:created_at).first
return unless on_waiting_list_user

on_waiting_list_user.update!(status: :registered)
send_email_confirmation(on_waiting_list_user, on_waiting_list_user.user, @meeting)

Decidim::EventsManager.publish(
event: "decidim.events.meetings.meeting_registration_confirmed",
event_class: Decidim::Meetings::MeetingRegistrationNotificationEvent,
resource: @meeting,
affected_users: [on_waiting_list_user.user],
extra: {
registration_code: on_waiting_list_user.code
}
)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@ def answer

@form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token:)

JoinMeeting.call(meeting, @form) do
waitlist = ActiveModel::Type::Boolean.new.cast(params[:waitlist])
command = waitlist ? JoinWaitlist : JoinMeeting

command.call(meeting, @form) do
on(:ok) do
flash[:notice] = I18n.t("registrations.create.success", scope: "decidim.meetings")
flash[:notice] = I18n.t("registrations.#{waitlist ? "waitlist" : "create"}.success", scope: "decidim.meetings")
redirect_to after_answer_path
end

on(:invalid) do
flash.now[:alert] = I18n.t("registrations.create.invalid", scope: "decidim.meetings")
render template: "decidim/forms/questionnaires/show"
flash.now[:alert] = I18n.t("registrations.#{waitlist ? "waitlist" : "create"}.invalid", scope: "decidim.meetings")
render template: "decidim/forms/questionnaires/show", status: :unprocessable_entity
end

on(:invalid_form) do
flash.now[:alert] = I18n.t("answer.invalid", scope: i18n_flashes_scope)
render template: "decidim/forms/questionnaires/show"
render template: "decidim/forms/questionnaires/show", status: :unprocessable_entity
end
end
end
Expand All @@ -47,17 +50,35 @@ def create
end
end

def join_waitlist
enforce_permission_to(:join_waitlist, :meeting, meeting:)

@form = JoinMeetingForm.from_params(params).with_context(current_user:)

JoinWaitlist.call(meeting, @form) do
on(:ok) do
flash[:notice] = I18n.t("registrations.waitlist.success", scope: "decidim.meetings")
redirect_after_path
end

on(:invalid) do
flash.now[:alert] = I18n.t("registrations.waitlist.invalid", scope: "decidim.meetings")
redirect_after_path
end
end
end

def destroy
enforce_permission_to(:leave, :meeting, meeting:)

status = registration.status
LeaveMeeting.call(meeting, current_user) do
on(:ok) do
flash[:notice] = I18n.t("registrations.destroy.success", scope: "decidim.meetings")
flash[:notice] = I18n.t("registrations.destroy.#{status}.success", scope: "decidim.meetings")
redirect_after_path
end

on(:invalid) do
flash.now[:alert] = I18n.t("registrations.destroy.invalid", scope: "decidim.meetings")
flash.now[:alert] = I18n.t("registrations.destroy.#{status}.invalid", scope: "decidim.meetings")
redirect_after_path
end
end
Expand All @@ -80,7 +101,9 @@ def decline_invitation
end

def allow_answers?
meeting.registrations_enabled? && meeting.registration_form_enabled? && meeting.has_available_slots?
return false unless meeting.registrations_enabled? && meeting.registration_form_enabled?

meeting.has_available_slots? || (meeting.waitlist_enabled? && request.path.include?("join_waitlist"))
end

def after_answer_path
Expand All @@ -90,7 +113,7 @@ def after_answer_path
# You can implement this method in your controller to change the URL
# where the questionnaire will be submitted.
def update_url
answer_meeting_registration_path(meeting_id: meeting.id)
answer_meeting_registration_path(meeting_id: meeting.id, waitlist: params[:waitlist] || request.path.include?("join_waitlist"))
end

def questionnaire_for
Expand All @@ -103,6 +126,10 @@ def meeting
@meeting ||= Meeting.where(component: current_component).find(params[:meeting_id])
end

def registration
@registration ||= meeting.registrations.find_by(user: current_user)
end

def redirect_after_path
redirect_to meeting_path(meeting)
end
Expand Down
Loading
Loading