decidim-system 0.0.1.alpha3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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" %>