Skip to content

Commit

Permalink
feat: Automatically vote initiatives after sign up / in when the moda…
Browse files Browse the repository at this point in the history
…l is used

- add new request param for auto sign when the modal is activated
- manage new param for standard sign in
- forward and manage new param for sign up
- forward and manage new param for france connect sign in
- forward and manage new param for france connect sign up with tos aggreement
- fix post logout redirect for france connect sign out 🔥
- add flash message to notify that the initiative was voted
  • Loading branch information
moustachu committed Dec 6, 2023
1 parent 38bff62 commit a7c9a33
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module Decidim
module OmniauthRegistrationsControllerOverride
extend ActiveSupport::Concern

included do
include Decidim::AfterSignInActionHelper

def after_sign_in_path_for(user)
after_sign_in_action_for(user, request.params[:after_action]) if request.params[:after_action].present?

if user.present? && user.blocked?
check_user_block_status(user)
elsif user.present? && !user.tos_accepted? && request.params[:after_action].present?
session["tos_after_action"] = request.params[:after_action]
super
elsif !pending_redirect?(user) && first_login_and_not_authorized?(user)
decidim_verifications.authorizations_path
else
super
end
end
end
end
end
16 changes: 16 additions & 0 deletions app/controllers/decidim/registrations_controller_override.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module Decidim
module RegistrationsControllerOverride
extend ActiveSupport::Concern

included do
include Decidim::AfterSignInActionHelper

def after_sign_in_path_for(user)
after_sign_in_action_for(user, request.params[:after_action]) if request.params[:after_action].present?
super
end
end
end
end
53 changes: 53 additions & 0 deletions app/helpers/decidim/after_sign_in_action_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

module Decidim
module AfterSignInActionHelper
extend ActiveSupport::Concern
include Decidim::FormFactory

included do
def default_url_options
url_options = {}
url_options[:locale] = current_locale unless current_locale == default_locale.to_s
url_options[:after_action] = request.params[:after_action] if request.params[:after_action].present?
url_options
end
end

def after_sign_in_action_for(user, action)
return if user.blank?
return unless action == "vote-initiative" && (scan = %r{/initiatives/i-(\d+)(\?.*)?}.match(read_stored_location_for(user)))

initiative = Decidim::Initiative.find(scan[1])

return unless allowed_to? :vote, :initiative, initiative: initiative, user: user, chain: permission_class_chain.push(Decidim::Initiatives::Permissions)

form = form(Decidim::Initiatives::VoteForm).from_params(
initiative: initiative,
signer: user
)

Decidim::Initiatives::VoteInitiative.call(form) do
on(:ok) do
after_action_flash_message!(:secondary, "initiative_votes.create.success", "decidim.initiatives")
end

on(:invalid) do
after_action_flash_message!(:error, "initiative_votes.create.error", "decidim.initiatives")
end
end
end

def read_stored_location_for(resource_or_scope)
store_location_for(resource_or_scope, stored_location_for(resource_or_scope))
end

def after_action_flash_message!(level, key, scope)
if is_a? DeviseController
set_flash_message! level, key, { scope: scope }
else
flash.now[level] = t(key, scope: scope)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module Decidim
module Initiatives
module InitiativeHelperVoteModalOverride
extend ActiveSupport::Concern

included do
def authorized_vote_modal_button(initiative, html_options, &block)
return if current_user && action_authorized_to("vote", resource: initiative, permissions_holder: initiative.type).ok?

tag = "button"
html_options ||= {}

html_options["data-after-action"] = "vote-initiative"

if current_user
html_options["data-open"] = "authorizationModal"
html_options["data-open-url"] = authorization_sign_modal_initiative_path(initiative)
else
html_options["data-open"] = "loginModal"
end

html_options["onclick"] = "event.preventDefault();"

send("#{tag}_to", "", html_options, &block)
end
end
end
end
end
81 changes: 81 additions & 0 deletions app/packs/src/decidim/append_after_action_to_modals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* eslint-disable multiline-ternary, no-ternary */

/*
* Triggered when adding data-after-action to a link or button
*
* This is used to add an after action after sign in.
*
* When a button or link trigger a login modal we capture
* the event and inject the after action to be done
* after sign in (the after_action param).
*
* The code is injected to any form or link in the modal
* and when the modal is closed we remove the injected
* code.
*
* In order for this to work the button or link must have
* a data-open attribute with the ID of the modal to open
* and a data-after-action attribute the action to be done.
* If any of this is missing no code will be injected.
*/
$(() => {
const removeAfterActionParameter = (url, parameter) => {
const urlParts = url.split("?");

if (urlParts.length >= 2) {
// Get first part, and remove from array
const urlBase = urlParts.shift();

// Join it back up
const queryString = urlParts.join("?");

const prefix = `${encodeURIComponent(parameter)}=`;
const parts = queryString.split(/[&;]/g);

// Reverse iteration as may be destructive
for (let index = parts.length - 1; index >= 0; index -= 1) {
// Idiom for string.startsWith
if (parts[index].lastIndexOf(prefix, 0) !== -1) {
parts.splice(index, 1);
}
}

if (parts.length === 0) {
return urlBase;
}

return `${urlBase}?${parts.join("&")}`;
}

return url;
}

$(document).on("click.zf.trigger", (event) => {
const target = `#${$(event.target).data("open")}`;
const afterAction = $(event.target).data("afterAction");

if (target && afterAction) {
$("<input type='hidden' />").
attr("id", "after_action").
attr("name", "after_action").
attr("value", afterAction).
appendTo(`${target} form`);

$(`${target} a`).attr("href", (index, href) => {
const querystring = jQuery.param({"after_action": afterAction});
return href + (href.match(/\?/) ? "&" : "?") + querystring;
});
}
});

$(document).on("closed.zf.reveal", (event) => {
$("#after_action", event.target).remove();
$("a", event.target).attr("href", (index, href) => {
if (href && href.indexOf("after_action") !== -1) {
return removeAfterActionParameter(href, "after_action");
}

return href;
});
});
});
2 changes: 2 additions & 0 deletions app/packs/src/decidim/decidim_application.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// This file is compiled inside Decidim core pack. Code can be added here and will be executed
// as part of that pack

import "src/decidim/append_after_action_to_modals"

// Load images
require.context("../../images", true)
2 changes: 2 additions & 0 deletions app/permissions/decidim/initiatives/permissions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def unvote_initiative?
can_unvote = initiative.accepts_online_unvotes? &&
initiative.organization&.id == user.organization&.id &&
initiative.votes.where(author: user).any? &&
user.tos_accepted? &&
authorized?(:vote, resource: initiative, permissions_holder: initiative.type)

toggle_allow(can_unvote)
Expand Down Expand Up @@ -181,6 +182,7 @@ def can_vote?
initiative.votes_enabled? &&
initiative.organization&.id == user.organization&.id &&
initiative.votes.where(author: user).empty? &&
user.tos_accepted? &&
authorized?(:vote, resource: initiative, permissions_holder: initiative.type)
end

Expand Down
10 changes: 10 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,22 @@ class Application < Rails::Application
config.after_initialize do
require "extends/forms/decidim/initiatives/initiative_form_extends"
require "extends/controllers/decidim/devise/sessions_controller_extends"
require "extends/controllers/decidim/homepage_controller_extends"
require "extends/forms/decidim/admin/organization_appearance_form_extends"
require "extends/omniauth/strategies/france_connect_extends"
end

initializer "session cookie domain", after: "Expire sessions" do
Rails.application.config.session_store :active_record_store, key: "_decidim_session", expire_after: Decidim.config.expire_session_after
ActiveRecord::SessionStore::Session.serializer = :hybrid
end

initializer "decidim_app.overrides", after: "decidim.action_controller" do
config.to_prepare do
Decidim::Initiatives::InitiativeHelper.include(Decidim::Initiatives::InitiativeHelperVoteModalOverride)
Decidim::Devise::RegistrationsController.include(Decidim::RegistrationsControllerOverride)
Decidim::Devise::OmniauthRegistrationsController.include(Decidim::OmniauthRegistrationsControllerOverride)
end
end
end
end
5 changes: 4 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ en:
verification_required: Verify your account to promote this initiative
initiatives:
show:
print: Voir le récépissé
print: Show receipt
initiative_votes:
create:
success: Congratulations! The initiative has been successfully signed
modal:
not_authorized:
authorizations_page: View authorizations
Expand Down
3 changes: 3 additions & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ fr:
initiatives:
show:
print: Voir le récépissé
initiative_votes:
create:
success: Toutes nos félicitations ! La pétition a été signée correctement
modal:
not_authorized:
authorizations_page: Voir les vérifications
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ module SessionControllerExtends
extend ActiveSupport::Concern

included do
include Decidim::AfterSignInActionHelper

def destroy
after_sign_out_url = after_sign_out_path_for(current_user)
current_user.invalidate_all_sessions!
if active_france_connect_session?
destroy_france_connect_session(session["omniauth.france_connect.end_session_uri"])
destroy_france_connect_session(session["omniauth.france_connect.end_session_uri"], after_sign_out_url)
elsif params[:translation_suffix].present?
super { set_flash_message! :notice, params[:translation_suffix], { scope: "decidim.devise.sessions" } }
else
Expand All @@ -16,6 +19,8 @@ def destroy
end

def after_sign_in_path_for(user)
after_sign_in_action_for(user, request.params[:after_action]) if request.params[:after_action].present?

if user.present? && user.blocked?
check_user_block_status(user)
elsif !skip_first_login_authorization? && (first_login_and_not_authorized?(user) && !user.admin? && !pending_redirect?(user))
Expand All @@ -33,13 +38,13 @@ def skip_first_login_authorization?
end
end

def destroy_france_connect_session(fc_logout_path)
def destroy_france_connect_session(fc_logout_path, post_logout_redirect_uri)
signed_out = (::Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
if signed_out
set_flash_message! :notice, :signed_out
session.delete("omniauth.france_connect.end_session_uri")
end

session["omniauth.france_connect.post_logout_redirect_uri"] = post_logout_redirect_uri
redirect_to fc_logout_path
end

Expand Down
21 changes: 21 additions & 0 deletions lib/extends/controllers/decidim/homepage_controller_extends.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module HomepageControllerExtends
extend ActiveSupport::Concern

included do
before_action :france_connect_after_sign_out, only: [:show]

def france_connect_after_sign_out
if session["omniauth.france_connect.post_logout_redirect_uri"].present?
post_logout_redirect_uri = session["omniauth.france_connect.post_logout_redirect_uri"]
session.delete("omniauth.france_connect.post_logout_redirect_uri")
redirect_to post_logout_redirect_uri
end
end
end
end

Decidim::HomepageController.class_eval do
include(HomepageControllerExtends)
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,22 @@ module InitiativesControllerExtends
extend ActiveSupport::Concern

included do
include Decidim::AfterSignInActionHelper

helper Decidim::Initiatives::SignatureTypeOptionsHelper
helper Decidim::Initiatives::InitiativePrintHelper

helper_method :available_initiative_types

def show
enforce_permission_to :read, :initiative, initiative: current_initiative
if session["tos_after_action"].present? && URI.parse(request.referer).path == tos_path.split("?")&.first
tos_after_action = session["tos_after_action"]
session.delete("tos_after_action")
after_sign_in_action_for(current_user, tos_after_action)
end
end

def print
enforce_permission_to :print, :initiative, initiative: current_initiative
end
Expand Down
17 changes: 17 additions & 0 deletions lib/extends/omniauth/strategies/france_connect_extends.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module FranceConnectExtends
extend ActiveSupport::Concern

included do
private

def redirect_uri
"#{omniauth_callback_url}?#{params.slice("redirect_uri", "after_action").to_query}"
end
end
end

OmniAuth::Strategies::FranceConnect.class_eval do
include(FranceConnectExtends)
end

0 comments on commit a7c9a33

Please sign in to comment.