decidim-friendly_signup 0.1 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +55 -20
- data/app/controllers/concerns/decidim/friendly_signup/needs_header_snippets.rb +2 -5
- data/app/controllers/concerns/decidim/friendly_signup/registrations_redirect.rb +37 -0
- data/app/controllers/decidim/friendly_signup/application_controller.rb +8 -0
- data/app/controllers/decidim/friendly_signup/confirmation_codes_controller.rb +50 -0
- data/app/controllers/decidim/friendly_signup/validator_controller.rb +18 -0
- data/app/forms/concerns/decidim/friendly_signup/auto_nickname.rb +35 -0
- data/app/forms/decidim/friendly_signup/confirmation_code_form.rb +33 -0
- data/app/mailers/decidim/friendly_signup/confirmation_codes_mailer.rb +22 -0
- data/app/models/concerns/decidim/friendly_signup/needs_registration_codes.rb +24 -0
- data/app/packs/entrypoints/decidim_friendly_signup.js +3 -1
- data/app/packs/entrypoints/decidim_friendly_signup.scss +1 -0
- data/app/packs/src/decidim/friendly_signup/lib/instant_validator.js +99 -0
- data/app/packs/src/decidim/friendly_signup/{password_toggler.js → lib/password_toggler.js} +0 -0
- data/app/packs/src/decidim/friendly_signup/setup_confirmations.js +57 -0
- data/app/packs/src/decidim/friendly_signup/{password_helper.js → setup_password.js} +1 -1
- data/app/packs/src/decidim/friendly_signup/setup_validations.js +7 -0
- data/app/packs/stylesheets/decidim/friendly_signup/_confirmation-codes.scss +23 -0
- data/app/views/decidim/devise/registrations/new.html.erb +10 -8
- data/app/views/decidim/friendly_signup/confirmation_codes/index.html.erb +38 -0
- data/app/views/decidim/friendly_signup/confirmation_codes_mailer/confirmation_instructions.html.erb +13 -0
- data/app/views/decidim/friendly_signup/shared/_password_fields.html.erb +1 -1
- data/config/locales/en.yml +79 -0
- data/lib/decidim/friendly_signup/engine.rb +21 -1
- data/lib/decidim/friendly_signup/user_attribute_validator.rb +49 -0
- data/lib/decidim/friendly_signup/version.rb +1 -1
- data/lib/decidim/friendly_signup.rb +32 -0
- data/package-lock.json +2 -2
- data/package.json +1 -1
- metadata +19 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df7a2d3137cd72541b5d7e63878f022eebb44e0b115044425e727c4f0fe68d59
|
4
|
+
data.tar.gz: dd63fddf8a154b335369dcf87cae2aaff1c9b28c9bd58ad1910a97088283b75f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a03470e2cb20fec61fe7106cd59d96293d9c9e490cd132570aa514a841c43587cbcf675ba25bfd272c5dec497db6c3f6a1922c170effb21f1ddeadeec252f331
|
7
|
+
data.tar.gz: 36919447d408e1a91f22b8d650b12db69ac00c08282fd5b07862bc074a344e9d99075d53fe53436af12ba80dc209b25e9085d3ca2bc0a219a1f47061d91566be
|
data/README.md
CHANGED
@@ -1,22 +1,26 @@
|
|
1
1
|
# Decidim::FriendlySignup
|
2
2
|
|
3
|
+
[![[CI] Lint](https://github.com/OpenSourcePolitics/decidim-module-friendly_signup/actions/workflows/lint.yml/badge.svg)](https://github.com/OpenSourcePolitics/decidim-module-friendly_signup/actions/workflows/lint.yml)
|
3
4
|
[![[CI] Test](https://github.com/OpenSourcePolitics/decidim-module-friendly_signup/actions/workflows/test.yml/badge.svg)](https://github.com/OpenSourcePolitics/decidim-module-friendly_signup/actions/workflows/test.yml)
|
4
5
|
[![Maintainability](https://api.codeclimate.com/v1/badges/46c261f70f7f49a8f385/maintainability)](https://codeclimate.com/github/OpenSourcePolitics/decidim-module-friendly_signup/maintainability)
|
5
6
|
[![Test Coverage](https://codecov.io/gh/OpenSourcePolitics/decidim-module-friendly_signup/branch/main/graph/badge.svg?token=1lrOiLdy9P)](https://codecov.io/gh/OpenSourcePolitics/decidim-module-friendly_signup)
|
7
|
+
[![Gem Version](https://badge.fury.io/rb/decidim-friendly_signup.svg)](https://badge.fury.io/rb/decidim-friendly_signup)
|
8
|
+
|
6
9
|
---
|
7
|
-
|
10
|
+
|
11
|
+
A more user friendly approach for the user registration process in [Decidim](https://github.com/decidim/decidim).
|
8
12
|
|
9
13
|
## Usage
|
10
14
|
|
11
15
|
This module simply substitutes some pages to ease up the registration process in Decidim.
|
12
16
|
|
13
|
-
Features:
|
17
|
+
### Features:
|
14
18
|
|
15
19
|
- [x] Simplify the password field and add a button with a "show password". ![Show/hide password](examples/passwords.png)
|
16
20
|
|
17
|
-
- [
|
18
|
-
- [
|
19
|
-
- [
|
21
|
+
- [x] Remove the nickname field from the registration process and automatically create one on registering. ![Hide nickname](examples/nickname.png)
|
22
|
+
- [x] Instant validate parameters when registering without having to send it for backend validation. ![Instant validation](examples/instant_validation.png)
|
23
|
+
- [x] Use numeric, confirmation codes to validate the email instead of a link. ![Confirmation codes](examples/confirmation_codes.png)
|
20
24
|
|
21
25
|
## Installation
|
22
26
|
|
@@ -38,9 +42,35 @@ And then execute:
|
|
38
42
|
bundle
|
39
43
|
```
|
40
44
|
|
45
|
+
For security reasons, it is also recomended to set a expiration time on confirmation tokens, to do that, make sure your Devise initializer has the variable `confirm_within` to certain amount of time.
|
46
|
+
|
47
|
+
For instance, you can do that by creating an initializer such as:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# config/initializers/devise.rb
|
51
|
+
|
52
|
+
Devise.setup do |config|
|
53
|
+
config.confirm_within = 12.hours
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
**Note:**
|
58
|
+
|
59
|
+
The correct version of FriendlySignup should resolved automatically by the Bundler.
|
60
|
+
However you can force some specific version using `gem "decidim-friendly_signup", "~> 0.1.0"` in the Gemfile.
|
61
|
+
|
62
|
+
Depending on your Decidim version, choose the corresponding FriendlySignup version to ensure compatibility:
|
63
|
+
|
64
|
+
| FriendlySignup version | Compatible Decidim versions |
|
65
|
+
|---|---|
|
66
|
+
| 0.4.x | 0.26.x |
|
67
|
+
| 0.3.x | 0.26.x |
|
68
|
+
| 0.2.x | 0.26.x |
|
69
|
+
| 0.1.x | 0.26.x |
|
70
|
+
|
41
71
|
## Configuration
|
42
72
|
|
43
|
-
Customize your integration by creating an initializer (ie: `config/initializes/friendly_signup.rb`) and set some of the variables:
|
73
|
+
Customize your integration by creating an initializer (ie: `config/initializes/friendly_signup.rb`) and set some of the variables (you don't need to do this if you want all features enabled):
|
44
74
|
|
45
75
|
```ruby
|
46
76
|
# config/initializers/friendly_signup.rb
|
@@ -48,14 +78,29 @@ Customize your integration by creating an initializer (ie: `config/initializes/f
|
|
48
78
|
Decidim::FriendlySignup.configure do |config|
|
49
79
|
# Override password views or leave the originals (default is true):
|
50
80
|
config.override_passwords = false
|
51
|
-
end
|
52
81
|
|
82
|
+
# Automatically validate user inputs in the register form (default is true):
|
83
|
+
config.use_instant_validation = false
|
84
|
+
|
85
|
+
# Hide nickname field and create one automatically from user's name or email (default is true)
|
86
|
+
config.hide_nickname = false
|
87
|
+
|
88
|
+
# Send the users a 4-digit number that needs to be entered in a confirmation page instead of a confirmation link (default is true)
|
89
|
+
config.use_confirmation_codes = false
|
90
|
+
end
|
53
91
|
```
|
54
92
|
|
55
93
|
## Contributing
|
56
94
|
|
57
95
|
Bug reports and pull requests are welcome on GitHub at https://github.com/OpenSourcePolitics/decidim-module-friendly_signup.
|
58
96
|
|
97
|
+
### Localization
|
98
|
+
|
99
|
+
If you would like to see this module in your own language, you can help with its
|
100
|
+
translation at Crowdin:
|
101
|
+
|
102
|
+
https://crowdin.com/project/decidim-friendly-signup
|
103
|
+
|
59
104
|
### Developing
|
60
105
|
|
61
106
|
To start contributing to this project, first:
|
@@ -133,23 +178,13 @@ commands shown above.
|
|
133
178
|
|
134
179
|
### Test code coverage
|
135
180
|
|
136
|
-
|
137
|
-
the `SIMPLECOV=1` environment variable in the rspec command as follows:
|
181
|
+
Test coverage should be generated automatically in the folder "coverage" once any test is run:
|
138
182
|
|
139
183
|
```bash
|
140
|
-
$
|
184
|
+
$ bundle exec rspec
|
185
|
+
$ firefox coverage/index.html
|
141
186
|
```
|
142
187
|
|
143
|
-
This will generate a folder named `coverage` in the project root which contains
|
144
|
-
the code coverage report.
|
145
|
-
|
146
|
-
### Localization
|
147
|
-
|
148
|
-
If you would like to see this module in your own language, you can help with its
|
149
|
-
translation at Crowdin:
|
150
|
-
|
151
|
-
https://crowdin.com/project/decidim-friendly-signup
|
152
|
-
|
153
188
|
## License
|
154
189
|
|
155
190
|
See [LICENSE-AGPLv3.txt](LICENSE-AGPLv3.txt).
|
@@ -23,11 +23,8 @@ module Decidim
|
|
23
23
|
@snippets
|
24
24
|
end
|
25
25
|
|
26
|
-
def friendly_override_activated?(
|
27
|
-
|
28
|
-
when :override_passwords
|
29
|
-
Decidim::FriendlySignup.override_passwords.present?
|
30
|
-
end
|
26
|
+
def friendly_override_activated?(feat)
|
27
|
+
Decidim::FriendlySignup.send(feat.to_s).present?
|
31
28
|
end
|
32
29
|
end
|
33
30
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Decidim
|
6
|
+
module FriendlySignup
|
7
|
+
module RegistrationsRedirect
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
def after_sign_up_path_for(user)
|
12
|
+
codes_confirmation_path(user) || super(user)
|
13
|
+
end
|
14
|
+
|
15
|
+
def after_inactive_sign_up_path_for(user)
|
16
|
+
codes_confirmation_path(user) || super(user)
|
17
|
+
end
|
18
|
+
|
19
|
+
def after_resending_confirmation_instructions_path_for(resource_name)
|
20
|
+
user = Decidim::User.find_by email: params.dig(:user, :email)
|
21
|
+
codes_confirmation_path(user) || super(resource_name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def codes_confirmation_path(user)
|
28
|
+
return if Decidim::FriendlySignup.use_confirmation_codes.blank?
|
29
|
+
return if user.blank?
|
30
|
+
return unless user.inactive_message.to_s == "unconfirmed"
|
31
|
+
|
32
|
+
set_flash_message! :notice, :signed_up_but_code_required
|
33
|
+
decidim_friendly_signup.confirmation_codes_path(confirmation_token: user.confirmation_token)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module FriendlySignup
|
5
|
+
class ConfirmationCodesController < Decidim::Devise::ConfirmationsController
|
6
|
+
include Decidim::FormFactory
|
7
|
+
include NeedsHeaderSnippets
|
8
|
+
|
9
|
+
before_action :require_unconfirmed_user
|
10
|
+
helper_method :user, :confirmation_form
|
11
|
+
|
12
|
+
def index; end
|
13
|
+
|
14
|
+
def create
|
15
|
+
if confirmation_form.valid?
|
16
|
+
user.confirm
|
17
|
+
flash[:success] = I18n.t("confirmation_codes.create.user_confirmed", name: user.name, scope: "decidim.friendly_signup")
|
18
|
+
return sign_in_and_redirect user, event: :authentication
|
19
|
+
end
|
20
|
+
|
21
|
+
flash.now[:alert] = confirmation_form.errors.messages.values.flatten.join(" ")
|
22
|
+
render :index
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def confirmation_form
|
28
|
+
@confirmation_form ||= form(ConfirmationCodeForm).from_params(params)
|
29
|
+
end
|
30
|
+
|
31
|
+
def user
|
32
|
+
@user ||= User.find_by(confirmation_token: params[:confirmation_token], organization: current_organization)
|
33
|
+
end
|
34
|
+
|
35
|
+
def require_unconfirmed_user
|
36
|
+
return redirect_to decidim.user_confirmation_path unless FriendlySignup.use_confirmation_codes
|
37
|
+
|
38
|
+
unless user.present? && !user.confirmed?
|
39
|
+
flash[:alert] = I18n.t("confirmation_code_form.invalid_token", scope: "decidim.friendly_signup")
|
40
|
+
return redirect_to decidim.new_user_session_path
|
41
|
+
end
|
42
|
+
|
43
|
+
if user.send(:confirmation_period_expired?)
|
44
|
+
flash[:alert] = I18n.t("confirmation_code_form.expired", scope: "decidim.friendly_signup")
|
45
|
+
redirect_to decidim.user_confirmation_path
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module FriendlySignup
|
5
|
+
class ValidatorController < ApplicationController
|
6
|
+
include Decidim::FormFactory
|
7
|
+
|
8
|
+
def validate
|
9
|
+
@form = form(Decidim::RegistrationForm).from_params(params)
|
10
|
+
validator = UserAttributeValidator.new(form: @form, attribute: params[:attribute])
|
11
|
+
render json: {
|
12
|
+
valid: validator.valid?,
|
13
|
+
error: validator.error
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Decidim
|
6
|
+
module FriendlySignup
|
7
|
+
module AutoNickname
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
def nickname
|
12
|
+
return super unless FriendlySignup.hide_nickname
|
13
|
+
|
14
|
+
UserBaseEntity.nicknamize(name || email, organization: current_organization)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# nickname is removed from the view, so put any (that shouldn't ever happen) error on the base
|
20
|
+
def nickname_unique_in_organization
|
21
|
+
return false unless nickname
|
22
|
+
|
23
|
+
if valid_users.find_by("LOWER(nickname)= ? AND decidim_organization_id = ?", nickname.downcase, current_organization.id).present?
|
24
|
+
errors.add :base, :taken
|
25
|
+
errors.add :nickname, :taken
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid_users
|
30
|
+
UserBaseEntity.where(invitation_token: nil)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module FriendlySignup
|
5
|
+
class ConfirmationCodeForm < Decidim::Form
|
6
|
+
attribute :confirmation_token, String
|
7
|
+
attribute :code, Integer
|
8
|
+
attribute :confirmation_numbers, Array[Integer]
|
9
|
+
|
10
|
+
validates :confirmation_token, presence: true
|
11
|
+
validate :code_matches_confirmation_token
|
12
|
+
validate :user_exists
|
13
|
+
|
14
|
+
def user_code
|
15
|
+
code || confirmation_numbers.map(&:to_s).join("").to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def code_matches_confirmation_token
|
21
|
+
return if FriendlySignup.confirmation_code(confirmation_token) == user_code
|
22
|
+
|
23
|
+
errors.add(:code, I18n.t("confirmation_code_form.invalid", scope: "decidim.friendly_signup"))
|
24
|
+
end
|
25
|
+
|
26
|
+
def user_exists
|
27
|
+
return if User.exists?(confirmation_token: confirmation_token, organization: current_organization)
|
28
|
+
|
29
|
+
errors.add(:code, I18n.t("confirmation_code_form.invalid_token", scope: "decidim.friendly_signup"))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module FriendlySignup
|
5
|
+
class ConfirmationCodesMailer < ApplicationMailer
|
6
|
+
include Decidim::LocalisedMailer
|
7
|
+
|
8
|
+
def confirmation_instructions(user, opts)
|
9
|
+
@user = user
|
10
|
+
@email = opts[:to] || user.email
|
11
|
+
@token = opts[:token]
|
12
|
+
@organization = user.organization
|
13
|
+
@code = FriendlySignup.confirmation_code(@token)
|
14
|
+
@expires_at = @user.confirmation_sent_at + @user.class.confirm_within if @user.class.confirm_within
|
15
|
+
|
16
|
+
with_user(user) do
|
17
|
+
mail(to: "#{user.name} <#{@email}>", subject: I18n.t("decidim.friendly_signup.confirmation_codes.mailer.subject", organization: @organization.name))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Decidim
|
6
|
+
module FriendlySignup
|
7
|
+
module NeedsRegistrationCodes
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
def send_confirmation_instructions
|
12
|
+
return super if Decidim::FriendlySignup.use_confirmation_codes.blank?
|
13
|
+
return super unless inactive_message.to_s == "unconfirmed"
|
14
|
+
|
15
|
+
generate_confirmation_token! unless @raw_confirmation_token
|
16
|
+
|
17
|
+
opts = pending_reconfirmation? ? { to: unconfirmed_email } : {}
|
18
|
+
opts[:token] = @raw_confirmation_token
|
19
|
+
ConfirmationCodesMailer.confirmation_instructions(self, opts).deliver_now
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
import "src/decidim/friendly_signup/
|
1
|
+
import "src/decidim/friendly_signup/setup_password"
|
2
|
+
import "src/decidim/friendly_signup/setup_validations"
|
3
|
+
import "src/decidim/friendly_signup/setup_confirmations"
|
2
4
|
|
3
5
|
// CSS
|
4
6
|
import "entrypoints/decidim_friendly_signup.scss";
|
@@ -0,0 +1,99 @@
|
|
1
|
+
/* eslint-disable line-comment-position, no-ternary, no-inline-comments */
|
2
|
+
|
3
|
+
// Instant, server-side validation
|
4
|
+
// compatible with abide classes https://get.foundation/sites/docs/abide.html
|
5
|
+
export default class InstantValidator {
|
6
|
+
// ms before xhr check
|
7
|
+
static get TIMEOUT() {
|
8
|
+
return 150;
|
9
|
+
}
|
10
|
+
|
11
|
+
constructor($form) {
|
12
|
+
this.$form = $form;
|
13
|
+
this.$inputs = $form.find("[data-instant-attribute]");
|
14
|
+
this.url = this.$form.data("validationUrl");
|
15
|
+
}
|
16
|
+
|
17
|
+
init() {
|
18
|
+
if (!this.url || !this.$form.length) {
|
19
|
+
return;
|
20
|
+
}
|
21
|
+
this.$form.foundation("disableValidation");
|
22
|
+
// this final validation prevents abide from resetting the field when user loses focus
|
23
|
+
this.$inputs.on("blur", (evt) => {
|
24
|
+
this.validate($(evt.currentTarget));
|
25
|
+
});
|
26
|
+
this.$inputs.on("keyup", (evt) => {
|
27
|
+
let $input = $(evt.currentTarget);
|
28
|
+
let checkTimeout = $input.data("checkTimeout");
|
29
|
+
// Trigger live validation with a delay to avoid throttling
|
30
|
+
if (checkTimeout) {
|
31
|
+
clearTimeout(checkTimeout);
|
32
|
+
}
|
33
|
+
$input.data("checkTimeout", setTimeout(() => {
|
34
|
+
this.validate($input);
|
35
|
+
}, this.TIMEOUT)
|
36
|
+
);
|
37
|
+
});
|
38
|
+
}
|
39
|
+
|
40
|
+
value($input) {
|
41
|
+
return $input.val().trim();
|
42
|
+
}
|
43
|
+
|
44
|
+
attribute($input) {
|
45
|
+
return $input.data("instantAttribute");
|
46
|
+
}
|
47
|
+
|
48
|
+
target($input) {
|
49
|
+
const $target = this.$form.find($input.data("instantTarget"));
|
50
|
+
return $target.length
|
51
|
+
? $target
|
52
|
+
: $input;
|
53
|
+
}
|
54
|
+
|
55
|
+
validate($input) {
|
56
|
+
this.tamper($input);
|
57
|
+
this.post($input).done((response) => {
|
58
|
+
this.setFeedback(response, $input);
|
59
|
+
});
|
60
|
+
}
|
61
|
+
|
62
|
+
setFeedback(data, $input) {
|
63
|
+
if (data.valid) {
|
64
|
+
this.clearErrors($input);
|
65
|
+
} else {
|
66
|
+
this.addErrors(this.target($input), data.error);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
tamper($dest) {
|
71
|
+
$dest.data("tampered", $dest.val().trim() !== "");
|
72
|
+
}
|
73
|
+
|
74
|
+
isTampered($dest) {
|
75
|
+
return $dest.data("tampered");
|
76
|
+
}
|
77
|
+
|
78
|
+
addErrors($dest, msg) {
|
79
|
+
if ($dest.closest("label").find(".form-error").length > 1) {
|
80
|
+
// Decidim may add and additional error class that does not play well with abide
|
81
|
+
$dest.closest("label").find(".form-error:last").remove();
|
82
|
+
}
|
83
|
+
this.$form.foundation("addErrorClasses", $dest);
|
84
|
+
if (msg) {
|
85
|
+
$dest.closest("label").find(".form-error").text(msg);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
clearErrors($dest) {
|
90
|
+
this.$form.foundation("removeErrorClasses", $dest);
|
91
|
+
}
|
92
|
+
|
93
|
+
post($input) {
|
94
|
+
return $.ajax(this.url, {
|
95
|
+
method: "POST",
|
96
|
+
data: `${this.$form.serialize()}&attribute=${this.attribute($input)}`
|
97
|
+
});
|
98
|
+
}
|
99
|
+
}
|
File without changes
|
@@ -0,0 +1,57 @@
|
|
1
|
+
$(() => {
|
2
|
+
const $inputs = $('.confirmation-code-inputs input[type="number"]');
|
3
|
+
const $form = $inputs.closest("form");
|
4
|
+
const intRegex = /^\d+$/;
|
5
|
+
let disableManual = false;
|
6
|
+
|
7
|
+
// Parses the individual digits into the individual boxes.
|
8
|
+
const pasteValues = (element, $first) => {
|
9
|
+
const values = element.split("");
|
10
|
+
let $inputBox = $first;
|
11
|
+
|
12
|
+
$(values).each((index) => {
|
13
|
+
$inputBox.val(values[index]);
|
14
|
+
$inputBox = $inputBox.next('input[type="number"]');
|
15
|
+
if ($inputBox.length === 0) {
|
16
|
+
$form.submit();
|
17
|
+
}
|
18
|
+
});
|
19
|
+
};
|
20
|
+
|
21
|
+
$inputs.on("focus", (e) => {
|
22
|
+
$(e.target).select();
|
23
|
+
});
|
24
|
+
|
25
|
+
// Prevents user from manually entering non-digits.
|
26
|
+
$inputs.on("input.fromManual", (e) => {
|
27
|
+
if (disableManual) {
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
const $this = $(e.target);
|
31
|
+
if (intRegex.test($this.val())) {
|
32
|
+
pasteValues($this.val(), $this);
|
33
|
+
$this.next('input[type="number"]').focus();
|
34
|
+
} else {
|
35
|
+
$this.val("");
|
36
|
+
}
|
37
|
+
});
|
38
|
+
|
39
|
+
$inputs.on("paste", (evt) => {
|
40
|
+
const $this = $(evt.target);
|
41
|
+
const originalValue = $this.val();
|
42
|
+
$this.val("");
|
43
|
+
disableManual = true;
|
44
|
+
$this.one("input.fromPaste", (e) => {
|
45
|
+
const $currentInputBox = $(e.target);
|
46
|
+
|
47
|
+
const pastedValue = $currentInputBox.val();
|
48
|
+
if (intRegex.test(pastedValue)) {
|
49
|
+
pasteValues(pastedValue, $inputs.eq(0));
|
50
|
+
}
|
51
|
+
else {
|
52
|
+
$this.val(originalValue);
|
53
|
+
}
|
54
|
+
disableManual = false;
|
55
|
+
});
|
56
|
+
});
|
57
|
+
});
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import InstantValidator from "src/decidim/friendly_signup/lib/instant_validator";
|
2
|
+
|
3
|
+
$(() => {
|
4
|
+
window.Decidim = window.Decidim || {};
|
5
|
+
window.Decidim.instantValidator = new InstantValidator($("form.instant-validation"));
|
6
|
+
window.Decidim.instantValidator.init();
|
7
|
+
});
|
@@ -0,0 +1,23 @@
|
|
1
|
+
.confirmation-code-inputs {
|
2
|
+
display: flex;
|
3
|
+
justify-content: center;
|
4
|
+
margin: 2rem;
|
5
|
+
|
6
|
+
input[type="number"] {
|
7
|
+
width: 15%;
|
8
|
+
font-size: 5em;
|
9
|
+
height: 1.2em;
|
10
|
+
line-height: 1;
|
11
|
+
padding: 0;
|
12
|
+
margin: 0 2%;
|
13
|
+
text-align: center;
|
14
|
+
box-shadow: 1px 3px 3px #bbb;
|
15
|
+
-moz-appearance: textfield;
|
16
|
+
|
17
|
+
&::-webkit-inner-spin-button,
|
18
|
+
&::-webkit-outer-spin-button {
|
19
|
+
-webkit-appearance: none;
|
20
|
+
margin: 0;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
@@ -26,7 +26,7 @@
|
|
26
26
|
<div class="row">
|
27
27
|
<div class="columns large-6 medium-10 medium-centered">
|
28
28
|
|
29
|
-
<%= decidim_form_for(@form, namespace: "registration", as: resource_name, url: registration_path(resource_name), html: { class: "register-form new_user", id: "register-form" }) do |f| %>
|
29
|
+
<%= decidim_form_for(@form, namespace: "registration", as: resource_name, url: registration_path(resource_name), html: { class: "register-form new_user#{friendly_override_activated?(:use_instant_validation) && ' instant-validation'}", id: "register-form" }, data: { "validation-url": decidim_friendly_signup.validate_path }) do |f| %>
|
30
30
|
<%= invisible_captcha %>
|
31
31
|
<div class="card">
|
32
32
|
<div class="card__content">
|
@@ -34,21 +34,23 @@
|
|
34
34
|
|
35
35
|
<div class="user-person">
|
36
36
|
<div class="field">
|
37
|
-
<%= f.text_field :name, help_text: t(".username_help") %>
|
37
|
+
<%= f.text_field :name, help_text: t(".username_help"), autocomplete: "off", data: { "instant-attribute": "nickname" } %>
|
38
38
|
</div>
|
39
39
|
</div>
|
40
40
|
|
41
|
-
|
42
|
-
<div class="
|
43
|
-
|
41
|
+
<% unless friendly_override_activated?(:hide_nickname) %>
|
42
|
+
<div class="user-nickname">
|
43
|
+
<div class="field">
|
44
|
+
<%= f.text_field :nickname, help_text: t(".nickname_help", organization: current_organization.name), prefix: { value: "@", small: 1, large: 1 }, autocomplete: "off", data: { "instant-attribute": "nickname" } %>
|
45
|
+
</div>
|
44
46
|
</div>
|
45
|
-
|
47
|
+
<% end %>
|
46
48
|
|
47
49
|
<div class="field">
|
48
|
-
<%= f.email_field :email %>
|
50
|
+
<%= f.email_field :email, autocomplete: "email", data: { "instant-attribute": "email" } %>
|
49
51
|
</div>
|
50
52
|
|
51
|
-
<%= render("decidim/friendly_signup/shared/password_fields", form: f, options: { help_text: t(".password_help", minimun_characters: ::PasswordValidator::MINIMUM_LENGTH), autocomplete: "off" }) %>
|
53
|
+
<%= render("decidim/friendly_signup/shared/password_fields", form: f, options: { required: true, help_text: t(".password_help", minimun_characters: ::PasswordValidator::MINIMUM_LENGTH), autocomplete: "off", data: { "instant-attribute": "password" } }) %>
|
52
54
|
</div>
|
53
55
|
</div>
|
54
56
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<% add_decidim_page_title(t(".enter_code")) %>
|
2
|
+
|
3
|
+
<div class="wrapper">
|
4
|
+
<div class="row collapse">
|
5
|
+
<div class="row collapse">
|
6
|
+
<div class="columns large-8 large-centered text-center page-title">
|
7
|
+
<h1><%= t(".title") %></h1>
|
8
|
+
<h6><%= t(".subtitle") %></h6>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<div class="row">
|
13
|
+
<div class="columns medium-10 large-8 medium-centered">
|
14
|
+
<div class="card">
|
15
|
+
<div class="card__content">
|
16
|
+
<p class="text-center"><%= t(".description", email: "<strong>#{user.email}</strong>").html_safe %></p>
|
17
|
+
<%= decidim_form_for(confirmation_form, url: decidim_friendly_signup.confirmation_codes_path(confirmation_token: confirmation_form.confirmation_token)) do |f| %>
|
18
|
+
<div class="field confirmation-code-inputs">
|
19
|
+
<%= number_field_tag "confirmation_numbers[0]", "", autofocus: true, autocomplete: :off %>
|
20
|
+
<%= number_field_tag "confirmation_numbers[1]", "", autocomplete: :off %>
|
21
|
+
<%= number_field_tag "confirmation_numbers[2]", "", autocomplete: :off %>
|
22
|
+
<%= number_field_tag "confirmation_numbers[3]", "", autocomplete: :off %>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<div class="actions text-center">
|
26
|
+
<%= f.submit t(".verify"), class: "button text-uppercase" %>
|
27
|
+
</div>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<p class="text-center">
|
31
|
+
<%= link_to t(".code_not_received"), decidim.user_confirmation_path(confirmation_token: confirmation_form.confirmation_token, "user[email]": user.email), method: :post, data: { confirm: t(".confirm_send_code", email: user.email) } %>
|
32
|
+
</p>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</div>
|
data/app/views/decidim/friendly_signup/confirmation_codes_mailer/confirmation_instructions.html.erb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
<h1 class="text-center"><%= t(".title") %></h1>
|
2
|
+
|
3
|
+
<p class="email-instructions text-center"><%= t(".subtitle", organization: link_to(@organization.name, decidim_friendly_signup.confirmation_codes_path(confirmation_token: @token))).html_safe %></p>
|
4
|
+
|
5
|
+
<h3 class="email-instruction text-center" style="margin:1em 0 0.2em 0;"><%= t(".copy") %></h3>
|
6
|
+
|
7
|
+
<% if @expires_at %>
|
8
|
+
<p class="email-small text-center"><%= t(".expires_in", time: distance_of_time_in_words(Time.now, @expires_at, scope: "decidim.friendly_signup.datetime.distance_in_words")) %></p>
|
9
|
+
<% end %>
|
10
|
+
|
11
|
+
<p class="stat text-center" style="margin:1em 0;"><b style="font-size: 2em"><%= @code %></b></p>
|
12
|
+
|
13
|
+
<p class="email-small email-closing text-center"><%= t(".ignore").html_safe %></p>
|
data/config/locales/en.yml
CHANGED
@@ -1,10 +1,89 @@
|
|
1
1
|
---
|
2
2
|
en:
|
3
|
+
activemodel:
|
4
|
+
attributes:
|
5
|
+
confirmation_code:
|
6
|
+
code: Confirmation code
|
3
7
|
decidim:
|
4
8
|
friendly_signup:
|
9
|
+
confirmation_code_form:
|
10
|
+
expired: Sorry, this code has expired, please generate a new one.
|
11
|
+
invalid: Code is invalid, please make sure to copy the code emailed to you.
|
12
|
+
invalid_token: Invalid token.
|
13
|
+
confirmation_codes:
|
14
|
+
create:
|
15
|
+
user_confirmed: Welcome %{name}! Your account has been succesfully confirmed!
|
16
|
+
index:
|
17
|
+
code_not_received: Didn't receive the code?
|
18
|
+
confirm_send_code: Do you want to resend the confirmation code to %{email}?<br>Be
|
19
|
+
sure to check the spam folder just in case!
|
20
|
+
description: You should receive a 4 digit code at %{email}.<br>If you can't
|
21
|
+
find it, please check your spam folder or wait up to 10 minutes.
|
22
|
+
enter_code: Enter code
|
23
|
+
subtitle: You need to confirm your email address to be able to submit proposals,
|
24
|
+
comment and vote.
|
25
|
+
title: One last step...
|
26
|
+
verify: Verify
|
27
|
+
mailer:
|
28
|
+
subject: Confirm your account at %{organization}
|
29
|
+
resend_code:
|
30
|
+
sent: The confirmation code has been sent to %{email}.
|
31
|
+
confirmation_codes_mailer:
|
32
|
+
confirmation_instructions:
|
33
|
+
copy: 'Copy this code:'
|
34
|
+
expires_in: It will expire in %{time}.
|
35
|
+
ignore: |-
|
36
|
+
If you didn't request this comunication, please ignore this email.<br />
|
37
|
+
Your account won't be active until your account is fully confirmed.
|
38
|
+
subtitle: To finalize the registration you just need to copy the 4 digit
|
39
|
+
code below, go back to the %{organization} signup page and paste it!
|
40
|
+
title: You're almost there!
|
41
|
+
datetime:
|
42
|
+
distance_in_words:
|
43
|
+
about_x_hours:
|
44
|
+
one: about 1 hour
|
45
|
+
other: about %{count} hours
|
46
|
+
about_x_months:
|
47
|
+
one: about 1 month
|
48
|
+
other: about %{count} months
|
49
|
+
about_x_years:
|
50
|
+
one: about 1 year
|
51
|
+
other: about %{count} years
|
52
|
+
almost_x_years:
|
53
|
+
one: almost 1 year
|
54
|
+
other: almost %{count} years
|
55
|
+
half_a_minute: half a minute
|
56
|
+
less_than_x_minutes:
|
57
|
+
one: less than a minute
|
58
|
+
other: less than %{count} minutes
|
59
|
+
less_than_x_seconds:
|
60
|
+
one: less than 1 second
|
61
|
+
other: less than %{count} seconds
|
62
|
+
over_x_years:
|
63
|
+
one: over 1 year
|
64
|
+
other: over %{count} years
|
65
|
+
x_days:
|
66
|
+
one: 1 day
|
67
|
+
other: "%{count} days"
|
68
|
+
x_minutes:
|
69
|
+
one: 1 minute
|
70
|
+
other: "%{count} minutes"
|
71
|
+
x_months:
|
72
|
+
one: 1 month
|
73
|
+
other: "%{count} months"
|
74
|
+
x_seconds:
|
75
|
+
one: 1 second
|
76
|
+
other: "%{count} seconds"
|
5
77
|
shared:
|
6
78
|
password_fields:
|
7
79
|
hidden_password: Your password is hidden
|
8
80
|
hide_password: Hide password
|
9
81
|
show_password: Show password
|
10
82
|
shown_password: Your password is shown
|
83
|
+
devise:
|
84
|
+
confirmations:
|
85
|
+
signed_up_but_code_required: A message with a code has been sent to your email
|
86
|
+
address. Please copy and paste the received code in this page.
|
87
|
+
registrations:
|
88
|
+
signed_up_but_code_required: A message with a code has been sent to your email
|
89
|
+
address. Please copy and paste the received code in this page.
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "rails"
|
4
4
|
require "decidim/core"
|
5
|
+
require "rack/attack"
|
5
6
|
|
6
7
|
module Decidim
|
7
8
|
module FriendlySignup
|
@@ -9,17 +10,36 @@ module Decidim
|
|
9
10
|
class Engine < ::Rails::Engine
|
10
11
|
isolate_namespace Decidim::FriendlySignup
|
11
12
|
|
13
|
+
routes do
|
14
|
+
devise_scope :user do
|
15
|
+
resources :confirmation_codes, only: [:index, :create]
|
16
|
+
end
|
17
|
+
post :validate, to: "validator#validate"
|
18
|
+
end
|
19
|
+
|
12
20
|
# Prepare a zone to create overrides
|
13
21
|
# https://edgeguides.rubyonrails.org/engines.html#overriding-models-and-controllers
|
14
22
|
# overrides
|
15
23
|
config.after_initialize do
|
16
24
|
Decidim::Devise::RegistrationsController.include(Decidim::FriendlySignup::NeedsHeaderSnippets)
|
25
|
+
Decidim::Devise::RegistrationsController.include(Decidim::FriendlySignup::RegistrationsRedirect)
|
26
|
+
Decidim::Devise::ConfirmationsController.include(Decidim::FriendlySignup::RegistrationsRedirect)
|
17
27
|
Decidim::Devise::InvitationsController.include(Decidim::FriendlySignup::NeedsHeaderSnippets)
|
18
28
|
Decidim::Devise::PasswordsController.include(Decidim::FriendlySignup::NeedsHeaderSnippets)
|
19
29
|
Decidim::AccountController.include(Decidim::FriendlySignup::NeedsHeaderSnippets)
|
30
|
+
Decidim::RegistrationForm.include(Decidim::FriendlySignup::AutoNickname)
|
31
|
+
Decidim::User.include(Decidim::FriendlySignup::NeedsRegistrationCodes)
|
32
|
+
end
|
33
|
+
|
34
|
+
initializer "friendly_signup.confirmation_throttling" do
|
35
|
+
# Throttle confirmation attempts for a given code parameter to 6 reqs/minute
|
36
|
+
# Return the confirmation_token as a discriminator on POST /users/sign_in requests
|
37
|
+
Rack::Attack.throttle("limit confirmations attempts per code", limit: 5, period: 60.seconds) do |request|
|
38
|
+
request.params["confirmation_token"] if request.path == "/friendly_signup/confirmation_codes" && request.post?
|
39
|
+
end
|
20
40
|
end
|
21
41
|
|
22
|
-
initializer "
|
42
|
+
initializer "friendly_signup.webpacker.assets_path" do
|
23
43
|
Decidim.register_assets_path File.expand_path("app/packs", root)
|
24
44
|
end
|
25
45
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module FriendlySignup
|
5
|
+
class UserAttributeValidator
|
6
|
+
def initialize(attribute:, form:)
|
7
|
+
@attribute = attribute
|
8
|
+
@form = form
|
9
|
+
end
|
10
|
+
|
11
|
+
delegate :current_organization, to: :form
|
12
|
+
attr_reader :attribute, :form
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
@valid ||= begin
|
16
|
+
form.validate
|
17
|
+
# we don't validate the form but the attribute alone
|
18
|
+
errors.blank?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def input
|
23
|
+
@input ||= form.public_send(attribute).to_s.dup if valid_attribute?
|
24
|
+
end
|
25
|
+
|
26
|
+
def errors
|
27
|
+
@errors ||= valid_attribute? ? form.errors[attribute] : ["Invalid attribute"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def error
|
31
|
+
errors.flatten.map(&:upcase_first).join(". ") unless valid?
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def valid_attribute?
|
37
|
+
%w(nickname email name password).include? attribute.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_suggestor?
|
41
|
+
["nickname"].include? attribute.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid_users
|
45
|
+
Decidim::UserBaseEntity.where(invitation_token: nil, organization: current_organization)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,14 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "decidim/friendly_signup/version"
|
3
4
|
require "decidim/friendly_signup/engine"
|
4
5
|
|
5
6
|
module Decidim
|
6
7
|
module FriendlySignup
|
7
8
|
include ActiveSupport::Configurable
|
8
9
|
|
10
|
+
autoload :UserAttributeValidator, "decidim/friendly_signup/user_attribute_validator"
|
11
|
+
|
9
12
|
# Whether to override passwords boxes or not
|
10
13
|
config_accessor :override_passwords do
|
11
14
|
true
|
12
15
|
end
|
16
|
+
|
17
|
+
# Whether to use instant validation or not
|
18
|
+
config_accessor :use_instant_validation do
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Whether to hide nickname and generate it automatically
|
23
|
+
config_accessor :hide_nickname do
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Use confirmation codes instead of confirmation links
|
28
|
+
config_accessor :use_confirmation_codes do
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Generates a secure code from a string
|
33
|
+
def self.confirmation_code(hash)
|
34
|
+
num = Decidim::Tokenizer.new(salt: Rails.application.secret_key_base).int_digest(hash).to_s[0..3]
|
35
|
+
num += "0" while num.size < 4
|
36
|
+
num.to_i
|
37
|
+
end
|
13
38
|
end
|
14
39
|
end
|
40
|
+
|
41
|
+
# Engines to handle logic unrelated to participatory spaces or components
|
42
|
+
Decidim.register_global_engine(
|
43
|
+
:decidim_friendly_signup, # this is the name of the global method to access engine routes
|
44
|
+
::Decidim::FriendlySignup::Engine,
|
45
|
+
at: "/friendly_signup"
|
46
|
+
)
|
data/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "decidim-module-friendly_signup",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.2.0",
|
4
4
|
"lockfileVersion": 2,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "decidim-module-friendly_signup",
|
9
|
-
"version": "0.
|
9
|
+
"version": "0.2.0",
|
10
10
|
"license": "AGPL-3.0-or-later",
|
11
11
|
"devDependencies": {
|
12
12
|
"eslint": "^7.25.0",
|
data/package.json
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: decidim-friendly_signup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.4'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Vergés
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-07-
|
11
|
+
date: 2022-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: decidim-core
|
@@ -49,22 +49,37 @@ files:
|
|
49
49
|
- README.md
|
50
50
|
- Rakefile
|
51
51
|
- app/controllers/concerns/decidim/friendly_signup/needs_header_snippets.rb
|
52
|
+
- app/controllers/concerns/decidim/friendly_signup/registrations_redirect.rb
|
53
|
+
- app/controllers/decidim/friendly_signup/application_controller.rb
|
54
|
+
- app/controllers/decidim/friendly_signup/confirmation_codes_controller.rb
|
55
|
+
- app/controllers/decidim/friendly_signup/validator_controller.rb
|
56
|
+
- app/forms/concerns/decidim/friendly_signup/auto_nickname.rb
|
57
|
+
- app/forms/decidim/friendly_signup/confirmation_code_form.rb
|
58
|
+
- app/mailers/decidim/friendly_signup/confirmation_codes_mailer.rb
|
59
|
+
- app/models/concerns/decidim/friendly_signup/needs_registration_codes.rb
|
52
60
|
- app/packs/entrypoints/decidim_friendly_signup.js
|
53
61
|
- app/packs/entrypoints/decidim_friendly_signup.scss
|
54
62
|
- app/packs/images/decidim/friendly_signup/icon.svg
|
55
|
-
- app/packs/src/decidim/friendly_signup/
|
56
|
-
- app/packs/src/decidim/friendly_signup/password_toggler.js
|
63
|
+
- app/packs/src/decidim/friendly_signup/lib/instant_validator.js
|
64
|
+
- app/packs/src/decidim/friendly_signup/lib/password_toggler.js
|
65
|
+
- app/packs/src/decidim/friendly_signup/setup_confirmations.js
|
66
|
+
- app/packs/src/decidim/friendly_signup/setup_password.js
|
67
|
+
- app/packs/src/decidim/friendly_signup/setup_validations.js
|
68
|
+
- app/packs/stylesheets/decidim/friendly_signup/_confirmation-codes.scss
|
57
69
|
- app/packs/stylesheets/decidim/friendly_signup/_input-groups.scss
|
58
70
|
- app/views/decidim/account/_password_fields.html.erb
|
59
71
|
- app/views/decidim/devise/invitations/edit.html.erb
|
60
72
|
- app/views/decidim/devise/passwords/edit.html.erb
|
61
73
|
- app/views/decidim/devise/registrations/new.html.erb
|
74
|
+
- app/views/decidim/friendly_signup/confirmation_codes/index.html.erb
|
75
|
+
- app/views/decidim/friendly_signup/confirmation_codes_mailer/confirmation_instructions.html.erb
|
62
76
|
- app/views/decidim/friendly_signup/shared/_password_fields.html.erb
|
63
77
|
- config/assets.rb
|
64
78
|
- config/i18n-tasks.yml
|
65
79
|
- config/locales/en.yml
|
66
80
|
- lib/decidim/friendly_signup.rb
|
67
81
|
- lib/decidim/friendly_signup/engine.rb
|
82
|
+
- lib/decidim/friendly_signup/user_attribute_validator.rb
|
68
83
|
- lib/decidim/friendly_signup/version.rb
|
69
84
|
- package-lock.json
|
70
85
|
- package.json
|