decidim-system 0.0.1.alpha3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of decidim-system might be problematic. Click here for more details.

Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +28 -0
  3. data/Rakefile +32 -0
  4. data/app/assets/config/decidim_system_manifest.js +2 -0
  5. data/app/assets/javascripts/decidim/system.js +21 -0
  6. data/app/assets/stylesheets/decidim/system/_actions.scss +8 -0
  7. data/app/assets/stylesheets/decidim/system/_foundation_and_overrides.scss +52 -0
  8. data/app/assets/stylesheets/decidim/system/_layout.scss +16 -0
  9. data/app/assets/stylesheets/decidim/system/_login.scss +36 -0
  10. data/app/assets/stylesheets/decidim/system/_settings.scss +566 -0
  11. data/app/assets/stylesheets/decidim/system/_sidebar.scss +73 -0
  12. data/app/assets/stylesheets/decidim/system/_tables.scss +5 -0
  13. data/app/assets/stylesheets/decidim/system.scss +8 -0
  14. data/app/commands/decidim/system/create_admin.rb +40 -0
  15. data/app/commands/decidim/system/register_organization.rb +59 -0
  16. data/app/commands/decidim/system/update_admin.rb +52 -0
  17. data/app/commands/decidim/system/update_organization.rb +51 -0
  18. data/app/controllers/decidim/system/admins_controller.rb +67 -0
  19. data/app/controllers/decidim/system/application_controller.rb +11 -0
  20. data/app/controllers/decidim/system/dashboard_controller.rb +9 -0
  21. data/app/controllers/decidim/system/devise/passwords_controller.rb +12 -0
  22. data/app/controllers/decidim/system/devise/sessions_controller.rb +12 -0
  23. data/app/controllers/decidim/system/organizations_controller.rb +59 -0
  24. data/app/forms/decidim/system/admin_form.rb +33 -0
  25. data/app/forms/decidim/system/register_organization_form.rb +20 -0
  26. data/app/forms/decidim/system/update_organization_form.rb +28 -0
  27. data/app/helpers/decidim/system/application_helper.rb +12 -0
  28. data/app/jobs/decidim/system/application_job.rb +9 -0
  29. data/app/mailers/decidim/system/application_mailer.rb +11 -0
  30. data/app/models/decidim/system/admin.rb +18 -0
  31. data/app/models/decidim/system/application_record.rb +10 -0
  32. data/app/views/decidim/system/admins/_form.html.erb +11 -0
  33. data/app/views/decidim/system/admins/edit.html.erb +11 -0
  34. data/app/views/decidim/system/admins/index.html.erb +33 -0
  35. data/app/views/decidim/system/admins/new.html.erb +11 -0
  36. data/app/views/decidim/system/admins/show.html.erb +9 -0
  37. data/app/views/decidim/system/dashboard/show.html.erb +3 -0
  38. data/app/views/decidim/system/devise/mailers/password_change.html.erb +3 -0
  39. data/app/views/decidim/system/devise/mailers/reset_password_instructions.html.erb +8 -0
  40. data/app/views/decidim/system/devise/passwords/edit.html.erb +23 -0
  41. data/app/views/decidim/system/devise/passwords/new.html.erb +15 -0
  42. data/app/views/decidim/system/devise/sessions/new.html.erb +23 -0
  43. data/app/views/decidim/system/devise/shared/_links.html.erb +25 -0
  44. data/app/views/decidim/system/organizations/edit.html.erb +17 -0
  45. data/app/views/decidim/system/organizations/index.html.erb +34 -0
  46. data/app/views/decidim/system/organizations/new.html.erb +25 -0
  47. data/app/views/decidim/system/organizations/show.html.erb +11 -0
  48. data/app/views/layouts/decidim/system/_header.html.erb +4 -0
  49. data/app/views/layouts/decidim/system/_login_items.html.erb +8 -0
  50. data/app/views/layouts/decidim/system/_sidebar.html.erb +15 -0
  51. data/app/views/layouts/decidim/system/application.html.erb +40 -0
  52. data/app/views/layouts/decidim/system/login.html.erb +19 -0
  53. data/config/i18n-tasks.yml +120 -0
  54. data/config/locales/en.yml +60 -0
  55. data/config/routes.rb +17 -0
  56. data/db/migrate/20160919105637_devise_create_decidim_admins.rb +26 -0
  57. data/db/seeds.rb +9 -0
  58. data/lib/decidim/system/engine.rb +31 -0
  59. data/lib/decidim/system.rb +12 -0
  60. data/lib/tasks/decidim/system_tasks.rake +5 -0
  61. metadata +317 -0
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ # A command with all the business logic when creating a new organization in
5
+ # the system. It creates the organization and invites the admin to the
6
+ # system.
7
+ class RegisterOrganization < Rectify::Command
8
+ # Public: Initializes the command.
9
+ #
10
+ # form - A form object with the params.
11
+ def initialize(form)
12
+ @form = form
13
+ end
14
+
15
+ # Executes the command. Braodcasts these events:
16
+ #
17
+ # - :ok when everything is valid.
18
+ # - :invalid if the form wasn't valid and we couldn't proceed.
19
+ #
20
+ # Returns nothing.
21
+ def call
22
+ return broadcast(:invalid) if form.invalid?
23
+
24
+ transaction do
25
+ organization = create_organization
26
+ invite_admin(organization)
27
+ end
28
+
29
+ broadcast(:ok)
30
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
31
+ broadcast(:invalid)
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :form
37
+
38
+ def create_organization
39
+ Decidim::Organization.create!(
40
+ name: form.name,
41
+ host: form.host,
42
+ description: form.description
43
+ )
44
+ end
45
+
46
+ def invite_admin(organization)
47
+ Decidim::User.invite!(
48
+ {
49
+ email: form.organization_admin_email,
50
+ organization: organization,
51
+ roles: ["admin"]
52
+ },
53
+ nil,
54
+ invitation_instructions: "organization_admin_invitation_instructions"
55
+ )
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ # A command with all the business logic when updating an admin in
5
+ # the system.
6
+ class UpdateAdmin < Rectify::Command
7
+ # Public: Initializes the command.
8
+ #
9
+ # form - A form object with the params.
10
+ def initialize(admin, form)
11
+ @admin = admin
12
+ @form = form
13
+ end
14
+
15
+ # Executes the command. Braodcasts these events:
16
+ #
17
+ # - :ok when everything is valid.
18
+ # - :invalid if the form wasn't valid and we couldn't proceed.
19
+ #
20
+ # Returns nothing.
21
+ def call
22
+ return broadcast(:invalid) if form.invalid?
23
+
24
+ update_admin
25
+ broadcast(:ok)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :form
31
+
32
+ def update_admin
33
+ @admin.update_attributes!(attributes)
34
+ end
35
+
36
+ def attributes
37
+ {
38
+ email: form.email
39
+ }.merge(password_attributes)
40
+ end
41
+
42
+ def password_attributes
43
+ return {} if form.password == form.password_confirmation && form.password.blank?
44
+
45
+ {
46
+ password: form.password,
47
+ password_confirmation: form.password_confirmation
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ # A command with all the business logic when creating a new organization in
5
+ # the system. It creates the organization and invites the admin to the
6
+ # system.
7
+ class UpdateOrganization < Rectify::Command
8
+ # Public: Initializes the command.
9
+ #
10
+ # form - A form object with the params.
11
+ def initialize(id, form)
12
+ @organization_id = id
13
+ @form = form
14
+ end
15
+
16
+ # Executes the command. Braodcasts these events:
17
+ #
18
+ # - :ok when everything is valid.
19
+ # - :invalid if the form wasn't valid and we couldn't proceed.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) if form.invalid?
24
+
25
+ transaction do
26
+ save_organization
27
+ end
28
+
29
+ broadcast(:ok)
30
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
31
+ broadcast(:invalid)
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :form, :organization_id
37
+
38
+ def organization
39
+ @organization ||= Organization.find(organization_id)
40
+ end
41
+
42
+ def save_organization
43
+ organization.name = form.name
44
+ organization.host = form.host
45
+ organization.description = form.description
46
+
47
+ organization.save!
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ require_dependency "decidim/system/application_controller"
3
+
4
+ module Decidim
5
+ module System
6
+ # Controller that allows managing all the Admins.
7
+ #
8
+ class AdminsController < ApplicationController
9
+ def index
10
+ @admins = Admin.all
11
+ end
12
+
13
+ def new
14
+ @form = AdminForm.new
15
+ end
16
+
17
+ def create
18
+ @form = AdminForm.from_params(params)
19
+
20
+ CreateAdmin.call(@form) do
21
+ on(:ok) do
22
+ flash[:notice] = I18n.t("admins.create.success", scope: "decidim.system")
23
+ redirect_to admins_path
24
+ end
25
+
26
+ on(:invalid) do
27
+ flash.now[:alert] = I18n.t("admins.create.error", scope: "decidim.system")
28
+ render :new
29
+ end
30
+ end
31
+ end
32
+
33
+ def edit
34
+ @admin = Admin.find(params[:id])
35
+ @form = AdminForm.from_model(@admin)
36
+ end
37
+
38
+ def update
39
+ @admin = Admin.find(params[:id])
40
+ @form = AdminForm.from_params(params)
41
+
42
+ UpdateAdmin.call(@admin, @form) do
43
+ on(:ok) do
44
+ flash[:notice] = I18n.t("admins.update.success", scope: "decidim.system")
45
+ redirect_to admins_path
46
+ end
47
+
48
+ on(:invalid) do
49
+ flash.now[:alert] = I18n.t("admins.update.error", scope: "decidim.system")
50
+ render :edit
51
+ end
52
+ end
53
+ end
54
+
55
+ def show
56
+ @admin = Admin.find(params[:id])
57
+ end
58
+
59
+ def destroy
60
+ @admin = Admin.find(params[:id]).destroy!
61
+ flash[:notice] = I18n.t("admins.destroy.success", scope: "decidim.system")
62
+
63
+ redirect_to admins_path
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ # The main application controller that inherits from Rails.
5
+ class ApplicationController < ActionController::Base
6
+ protect_from_forgery with: :exception, prepend: true
7
+
8
+ helper Decidim::TranslationsHelper
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ require_dependency "decidim/system/application_controller"
3
+
4
+ module Decidim
5
+ module System
6
+ class DashboardController < ApplicationController
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ module Devise
5
+ # Custom Passwords controller for Devise in order to use a custom layout
6
+ # and views.
7
+ class PasswordsController < ::Devise::PasswordsController
8
+ layout "decidim/system/login"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ module Devise
5
+ # Custom Sessions controller for Devise in order to use a custom layout
6
+ # and views.
7
+ class SessionsController < ::Devise::SessionsController
8
+ layout "decidim/system/login"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+ require_dependency "decidim/system/application_controller"
3
+
4
+ module Decidim
5
+ module System
6
+ # Controller to manage Organizations (tenants).
7
+ #
8
+ class OrganizationsController < ApplicationController
9
+ def new
10
+ @form = RegisterOrganizationForm.new
11
+ end
12
+
13
+ def create
14
+ @form = RegisterOrganizationForm.from_params(params)
15
+
16
+ RegisterOrganization.call(@form) do
17
+ on(:ok) do
18
+ flash[:notice] = t("organizations.create.success", scope: "decidim.system")
19
+ redirect_to organizations_path
20
+ end
21
+
22
+ on(:invalid) do
23
+ flash.now[:alert] = t("organizations.create.error", scope: "decidim.system")
24
+ render :new
25
+ end
26
+ end
27
+ end
28
+
29
+ def index
30
+ @organizations = Organization.all
31
+ end
32
+
33
+ def show
34
+ @organization = Organization.find(params[:id])
35
+ end
36
+
37
+ def edit
38
+ organization = Organization.find(params[:id])
39
+ @form = UpdateOrganizationForm.from_model(organization)
40
+ end
41
+
42
+ def update
43
+ @form = UpdateOrganizationForm.from_params(params)
44
+
45
+ UpdateOrganization.call(params[:id], @form) do
46
+ on(:ok) do
47
+ flash[:notice] = t("organizations.update.success", scope: "decidim.system")
48
+ redirect_to organizations_path
49
+ end
50
+
51
+ on(:invalid) do
52
+ flash.now[:alert] = I18n.t("organizations.update.error", scope: "decidim.system")
53
+ render :edit
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ # A form object used to create organizations from the system dashboard.
5
+ #
6
+ class AdminForm < Rectify::Form
7
+ mimic :admin
8
+
9
+ attribute :email, String
10
+ attribute :password, String
11
+ attribute :password_confirmation, String
12
+
13
+ validates :email, presence: true
14
+ validates :password, presence: true, unless: :admin_exists?
15
+ validates :password_confirmation, presence: true, unless: :admin_exists?
16
+
17
+ validate :email, :email_uniqueness
18
+
19
+ private
20
+
21
+ def email_uniqueness
22
+ return unless Admin.where(email: email).where.not(id: id).any?
23
+
24
+ errors.add(:email, I18n.t("models.admin.validations.email_uniqueness",
25
+ scope: "decidim.system"))
26
+ end
27
+
28
+ def admin_exists?
29
+ id.present?
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ require "decidim/translatable_attributes"
3
+
4
+ module Decidim
5
+ module System
6
+ # A form object used to create organizations from the system dashboard.
7
+ #
8
+ class RegisterOrganizationForm < UpdateOrganizationForm
9
+ include TranslatableAttributes
10
+
11
+ mimic :organization
12
+
13
+ attribute :organization_admin_email, String
14
+
15
+ validates :organization_admin_email, presence: true
16
+
17
+ translatable_attribute :description, String
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require "decidim/translatable_attributes"
3
+
4
+ module Decidim
5
+ module System
6
+ # A form object used to update organizations from the system dashboard.
7
+ #
8
+ class UpdateOrganizationForm < Rectify::Form
9
+ include TranslatableAttributes
10
+
11
+ mimic :organization
12
+
13
+ attribute :name, String
14
+ attribute :host, String
15
+
16
+ translatable_attribute :description, String
17
+
18
+ validate :validate_organization_uniqueness
19
+
20
+ private
21
+
22
+ def validate_organization_uniqueness
23
+ errors.add(:name, :taken) if Decidim::Organization.where(name: name).where.not(id: id).exists?
24
+ errors.add(:host, :taken) if Decidim::Organization.where(host: host).where.not(id: id).exists?
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ # Custom helpers, scoped to the system panel.
5
+ #
6
+ module ApplicationHelper
7
+ def title
8
+ "Decidim"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ # Custom ApplicationJob scoped to the system panel.
5
+ #
6
+ class ApplicationJob < ActiveJob::Base
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ # Custom application mailer, scoped to the system mailer.
5
+ #
6
+ class ApplicationMailer < ActionMailer::Base
7
+ default from: "from@example.com"
8
+ layout "mailer"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ # Admins are the users in charge of managing a Decidim installation.
5
+ class Admin < ApplicationRecord
6
+ devise :database_authenticatable, :recoverable, :rememberable, :validatable
7
+
8
+ validates :email, uniqueness: true
9
+
10
+ private
11
+
12
+ # Changes default Devise behaviour to use ActiveJob to send async emails.
13
+ def send_devise_notification(notification, *args)
14
+ devise_mailer.send(notification, self, *args).deliver_later
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module System
4
+ # Custom ApplicationRecord scoped to the system panel.
5
+ #
6
+ class ApplicationRecord < ActiveRecord::Base
7
+ self.abstract_class = true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ <div class="field">
2
+ <%= form.text_field :email, autofocus: true %>
3
+ </div>
4
+
5
+ <div class="field">
6
+ <%= form.password_field :password %>
7
+ </div>
8
+
9
+ <div class="field">
10
+ <%= form.password_field :password_confirmation %>
11
+ </div>
@@ -0,0 +1,11 @@
1
+ <% content_for :title do %>
2
+ <h2><%= t ".title" %></h2>
3
+ <% end %>
4
+
5
+ <%= form_for(@form) do |f| %>
6
+ <%= render partial: 'form', object: f %>
7
+
8
+ <div class="actions">
9
+ <%= f.submit t(".update") %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,33 @@
1
+ <% content_for :title do %>
2
+ <h2><%= t ".title" %></h2>
3
+ <% end %>
4
+
5
+ <div class="actions title">
6
+ <%= link_to t("actions.new", scope: "decidim.system", name: t("models.admin.name", scope: "decidim.system")), ['new', 'admin'], class: 'new' %>
7
+ </div>
8
+
9
+ <table class="stack">
10
+ <thead>
11
+ <tr>
12
+ <th><%= t("models.admin.fields.email", scope: "decidim.system") %></th>
13
+ <th><%= t("models.admin.fields.created_at", scope: "decidim.system") %></th>
14
+ <th class="actions"><%= t("actions.title", scope: "decidim.system") %></th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ <% @admins.each do |admin| %>
19
+ <tr>
20
+ <td>
21
+ <%= link_to admin.email, admin %><br />
22
+ </td>
23
+ <td>
24
+ <%= l admin.created_at, format: :short %>
25
+ </td>
26
+ <td class="actions">
27
+ <%= link_to t("actions.edit", scope: "decidim.system"), ['edit', admin] %>
28
+ <%= link_to t("actions.destroy", scope: "decidim.system"), admin, method: :delete, class: "small alert button", data: { confirm: t("actions.confirm_destroy", scope: "decidim.system") } %>
29
+ </td>
30
+ </tr>
31
+ <% end %>
32
+ </tbody>
33
+ </table>
@@ -0,0 +1,11 @@
1
+ <% content_for :title do %>
2
+ <h2><%= t ".title" %></h2>
3
+ <% end %>
4
+
5
+ <%= form_for(@form) do |f| %>
6
+ <%= render partial: 'form', object: f %>
7
+
8
+ <div class="actions">
9
+ <%= f.submit t(".create") %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% content_for :title do %>
2
+ <h2><%= link_to @admin.email, @admin %></h2>
3
+ <% end %>
4
+
5
+ <div class="actions">
6
+ <hr />
7
+ <%= link_to "Edit", ['edit', @admin] %>
8
+ <%= link_to "Destroy", @admin, method: :delete, class: "alert button", data: { confirm: "Are you sure you want to do this?" } %>
9
+ </div>
@@ -0,0 +1,3 @@
1
+ <% content_for :title do %>
2
+ <h2><%= t("decidim.system.titles.dashboard") %></h2>
3
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <p>Hello <%= @resource.email %>!</p>
2
+
3
+ <p>We're contacting you to notify you that your password has been changed.</p>
@@ -0,0 +1,8 @@
1
+ <p>Hello <%= @resource.email %>!</p>
2
+
3
+ <p>Someone has requested a link to change your password. You can do this through the link below.</p>
4
+
5
+ <p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
6
+
7
+ <p>If you didn't request this, please ignore this email.</p>
8
+ <p>Your password won't change until you access the link above and create a new one.</p>
@@ -0,0 +1,23 @@
1
+ <h2>Change your password</h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
4
+ <%= devise_error_messages! %>
5
+ <%= f.hidden_field :reset_password_token %>
6
+
7
+ <div class="field">
8
+ <% if @minimum_password_length %>
9
+ <em>(<%= @minimum_password_length %> characters minimum)</em><br />
10
+ <% end %>
11
+ <%= f.password_field :password, autofocus: true, autocomplete: "off" %>
12
+ </div>
13
+
14
+ <div class="field">
15
+ <%= f.password_field :password_confirmation, autocomplete: "off" %>
16
+ </div>
17
+
18
+ <div class="actions">
19
+ <%= f.submit "Change my password" %>
20
+ </div>
21
+ <% end %>
22
+
23
+ <%= render "decidim/system/devise/shared/links" %>
@@ -0,0 +1,15 @@
1
+ <h2>Forgot your password?</h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
4
+ <%= devise_error_messages! %>
5
+
6
+ <div class="field">
7
+ <%= f.email_field :email, autofocus: true %>
8
+ </div>
9
+
10
+ <div class="actions">
11
+ <%= f.submit "Send me reset password instructions" %>
12
+ </div>
13
+ <% end %>
14
+
15
+ <%= render "decidim/system/devise/shared/links" %>
@@ -0,0 +1,23 @@
1
+ <h2>Log in</h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
4
+ <div class="field">
5
+ <%= f.email_field :email, autofocus: true %>
6
+ </div>
7
+
8
+ <div class="field">
9
+ <%= f.password_field :password, autocomplete: "off" %>
10
+ </div>
11
+
12
+ <% if devise_mapping.rememberable? -%>
13
+ <div class="field">
14
+ <%= f.check_box :remember_me %>
15
+ </div>
16
+ <% end -%>
17
+
18
+ <div class="actions">
19
+ <%= f.submit "Log in" %>
20
+ </div>
21
+ <% end %>
22
+
23
+ <%= render "decidim/system/devise/shared/links" %>