bullet_train 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/registrations/controller_base.rb +39 -0
  3. data/app/controllers/concerns/sessions/controller_base.rb +15 -0
  4. data/app/controllers/registrations_controller.rb +9 -0
  5. data/app/controllers/sessions_controller.rb +3 -0
  6. data/app/helpers/account/buttons_helper.rb +12 -0
  7. data/app/helpers/account/dates_helper.rb +38 -0
  8. data/app/helpers/account/forms_helper.rb +67 -0
  9. data/app/helpers/account/locale_helper.rb +51 -0
  10. data/app/helpers/account/markdown_helper.rb +5 -0
  11. data/app/helpers/account/role_helper.rb +5 -0
  12. data/app/helpers/attributes_helper.rb +32 -0
  13. data/app/helpers/email_helper.rb +7 -0
  14. data/app/helpers/images_helper.rb +7 -0
  15. data/app/mailers/concerns/mailers/base.rb +16 -0
  16. data/app/mailers/devise_mailer.rb +10 -0
  17. data/app/mailers/user_mailer.rb +24 -0
  18. data/app/views/devise/confirmations/new.html.erb +16 -0
  19. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  20. data/app/views/devise/mailer/password_change.html.erb +3 -0
  21. data/app/views/devise/mailer/reset_password_instructions.html.erb +30 -0
  22. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  23. data/app/views/devise/passwords/edit.html.erb +17 -0
  24. data/app/views/devise/passwords/new.html.erb +20 -0
  25. data/app/views/devise/registrations/_two_factor.html.erb +42 -0
  26. data/app/views/devise/registrations/edit.html.erb +43 -0
  27. data/app/views/devise/registrations/new.html.erb +30 -0
  28. data/app/views/devise/sessions/new.html.erb +59 -0
  29. data/app/views/devise/sessions/pre_otp.js.erb +11 -0
  30. data/app/views/devise/shared/_links.html.erb +21 -0
  31. data/app/views/devise/shared/_oauth.html.erb +9 -0
  32. data/app/views/devise/unlocks/new.html.erb +16 -0
  33. data/config/locales/en/devise.en.yml +110 -0
  34. data/lib/bullet_train/version.rb +1 -1
  35. metadata +34 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 633b451afdfeee512883e8536928cc2dd247f15139d59e7bfd24cc4d637b735d
4
- data.tar.gz: c2a59912f00000e2b72ae5193b6d5cd0ebcd34a736f01260f5abfa1a5c170c94
3
+ metadata.gz: edaadd5d50965bf9b15be0e2c69ead2e4180ab34a1cba61035546546dd00bb87
4
+ data.tar.gz: 0b41c024ef616cd3a33b114da7dca2f0c0161713003269569a210e2937814004
5
5
  SHA512:
6
- metadata.gz: 6cacd920c5c13800b167a1d1c3e9ae8fa6d6bc1cea28d1128f7404ce8bd9e0615957aad91b4cb7363bb10ac88823294511344bf9103febe2b0ed830508472cba
7
- data.tar.gz: 1bc0e30c37ebd765df530a9d25b65a591736be8420949c68651569347280d7403d3889cdd59933384825c19d5727a25c7d16569a42e9baececfeb2970938b85a
6
+ metadata.gz: 0c599a9871fdf7b746ac62452489554e271c62a8384ca6f747bb512f0334bdd22d6ec78e9fb6218d2a78a17f26afe343a78e70aff0fd4c9d7d5992f8a2a322fe
7
+ data.tar.gz: ca2f4b81605161e94f3a704d63bdcbcbced15228688f2ee454e1db9876d1d198e56e9ba9f3b5f34f2c7b1841fa5ac655bba5dedd1e5bf6cd7016a6d1c19414cf
@@ -0,0 +1,39 @@
1
+ module Registrations::ControllerBase
2
+ extend ActiveSupport::Concern
3
+
4
+ include do
5
+ def new
6
+ if invitation_only?
7
+ unless session[:invitation_uuid] || session[:invitation_key]
8
+ return redirect_to root_path
9
+ end
10
+ end
11
+
12
+ # do all the regular devise stuff.
13
+ super
14
+ end
15
+
16
+ def create
17
+ # do all the regular devise stuff first.
18
+ super
19
+
20
+ # if current_user is defined, that means they were successful registering.
21
+ if current_user
22
+
23
+ # TODO i think this might be redundant. we've added a hook into `session["user_return_to"]` in the
24
+ # `invitations#accept` action and that might be enough to get them where they're supposed to be after
25
+ # either creating a new account or signing into an existing account.
26
+ handle_outstanding_invitation
27
+
28
+ # if the user doesn't have a team at this point, create one.
29
+ unless current_user.teams.any?
30
+ current_user.create_default_team
31
+ end
32
+
33
+ # send the welcome email.
34
+ current_user.send_welcome_email unless current_user.email_is_oauth_placeholder?
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module Sessions::ControllerBase
2
+ extend ActiveSupport::Concern
3
+
4
+ include do
5
+ def pre_otp
6
+ if (@email = params["user"]["email"].downcase.strip.presence)
7
+ @user = User.find_by(email: @email)
8
+ end
9
+
10
+ respond_to do |format|
11
+ format.js
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # i really, really wanted this controller in a namespace, but devise doesn't
2
+ # appear to support it. instead, i got the following error:
3
+ #
4
+ # 'Authentication::RegistrationsController' is not a supported controller name.
5
+ # This can lead to potential routing problems.
6
+
7
+ class RegistrationsController < Devise::RegistrationsController
8
+ include Registrations::ControllerBase
9
+ end
@@ -0,0 +1,3 @@
1
+ class SessionsController < Devise::SessionsController
2
+ include Sessions::ControllerBase
3
+ end
@@ -0,0 +1,12 @@
1
+ module Account::ButtonsHelper
2
+ def first_button_primary(context = nil)
3
+ @global ||= {}
4
+
5
+ if !@global[context]
6
+ @global[context] = true
7
+ "button"
8
+ else
9
+ "button-secondary"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,38 @@
1
+ module Account::DatesHelper
2
+ # e.g. October 11, 2018
3
+ def display_date(timestamp)
4
+ return nil unless timestamp
5
+ if local_time(timestamp).year == local_time(Time.now).year
6
+ local_time(timestamp).strftime("%B %-d")
7
+ else
8
+ local_time(timestamp).strftime("%B %-d, %Y")
9
+ end
10
+ end
11
+
12
+ # e.g. October 11, 2018 at 4:22 PM
13
+ # e.g. Yesterday at 2:12 PM
14
+ # e.g. April 24 at 7:39 AM
15
+ def display_date_and_time(timestamp)
16
+ return nil unless timestamp
17
+
18
+ # today?
19
+ if local_time(timestamp).to_date == local_time(Time.now).to_date
20
+ "Today at #{display_time(timestamp)}"
21
+ # yesterday?
22
+ elsif (local_time(timestamp).to_date) == (local_time(Time.now).to_date - 1.day)
23
+ "Yesterday at #{display_time(timestamp)}"
24
+ else
25
+ "#{display_date(timestamp)} at #{display_time(timestamp)}"
26
+ end
27
+ end
28
+
29
+ # e.g. 4:22 PM
30
+ def display_time(timestamp)
31
+ local_time(timestamp).strftime("%l:%M %p")
32
+ end
33
+
34
+ def local_time(time)
35
+ return time if current_user.time_zone.nil?
36
+ time.in_time_zone(current_user.time_zone)
37
+ end
38
+ end
@@ -0,0 +1,67 @@
1
+ module Account::FormsHelper
2
+ PRESENCE_VALIDATORS = [ActiveRecord::Validations::PresenceValidator, ActiveModel::Validations::PresenceValidator]
3
+
4
+ def presence_validated?(object, attribute)
5
+ validators = object.class.validators
6
+ validators.select! do |validator|
7
+ PRESENCE_VALIDATORS.include?(validator.class) && validator.attributes.include?(attribute)
8
+ end
9
+ validators.any?
10
+ end
11
+
12
+ def flush_content_for(name)
13
+ content_for name, flush: true do
14
+ ""
15
+ end
16
+ end
17
+
18
+ def options_with_labels(options, namespace)
19
+ hash = {}
20
+ options.each do |option|
21
+ hash[option] = t([namespace, option].join("."))
22
+ end
23
+ hash
24
+ end
25
+
26
+ def if_present(string)
27
+ string.present? ? string : nil
28
+ end
29
+
30
+ def id_for(form, method)
31
+ [form.object.class.name, form.index, method].compact.join("_").underscore
32
+ end
33
+
34
+ def model_key(form)
35
+ form.object.class.name.pluralize.underscore
36
+ end
37
+
38
+ def labels_for(form, method)
39
+ keys = [:placeholder, :label, :help, :options_help]
40
+ path = [model_key(form), (current_fields_namespace || :fields), method].compact
41
+ Struct.new(*keys).new(*keys.map { |key| t((path + [key]).join("."), default: "").presence })
42
+ end
43
+
44
+ def options_for(form, method)
45
+ # e.g. "scaffolding/completely_concrete/tangible_things.fields.text_area_value.options"
46
+ path = [model_key(form), (current_fields_namespace || :fields), method, :options]
47
+ t(path.compact.join("."))
48
+ end
49
+
50
+ def legacy_label_for(form, method)
51
+ # e.g. 'scaffolding/things.labels.name'
52
+ key = "#{model_key(form)}.labels.#{method}"
53
+ # e.g. 'scaffolding/things.labels.name' or 'scaffolding.things.labels.name' or nil
54
+ t(key, default: "").presence || t(key.tr("/", "."), default: "").presence
55
+ end
56
+
57
+ def within_fields_namespace(namespace)
58
+ @fields_namespaces ||= []
59
+ @fields_namespaces << namespace
60
+ yield
61
+ @fields_namespaces.pop
62
+ end
63
+
64
+ def current_fields_namespace
65
+ @fields_namespaces&.last
66
+ end
67
+ end
@@ -0,0 +1,51 @@
1
+ module Account::LocaleHelper
2
+ def current_locale
3
+ current_user.locale || current_team.locale || "en"
4
+ end
5
+
6
+ # as of now, we only calculate a possessive version of nouns in english.
7
+ # if you're aware of another language where we can do this, please don't hesitate to reach out!
8
+ def possessive_string(string)
9
+ [:en].include?(I18n.locale) ? string.possessive : string
10
+ end
11
+
12
+ def model_locales(model)
13
+ name = model.label_string.presence
14
+ return {} unless name
15
+
16
+ hash = {}
17
+ prefix = model.class.name.split("::").last.underscore
18
+ hash[:"#{prefix}_name"] = name
19
+ hash[:"#{prefix.pluralize}_possessive"] = possessive_string(name)
20
+
21
+ hash
22
+ end
23
+
24
+ def models_locales(*models)
25
+ hash = {}
26
+ models.compact.each do |model|
27
+ hash.merge! model_locales(model)
28
+ end
29
+ hash
30
+ end
31
+
32
+ # this is a bit scary, no?
33
+ def account_controller?
34
+ controller.class.name.match(/^Account::/)
35
+ end
36
+
37
+ def t(key, options = {})
38
+ if account_controller?
39
+ # give preference to the options they've passed in.
40
+ options = models_locales(@child_object, @parent_object).merge(options)
41
+ end
42
+ super(key, options)
43
+ end
44
+
45
+ # like 't', but if the key isn't found, it returns nil.
46
+ def ot(key, options = {})
47
+ t(key, options)
48
+ rescue I18n::MissingTranslationData => _
49
+ nil
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ module Account::MarkdownHelper
2
+ def markdown(string)
3
+ CommonMarker.render_html(string, :UNSAFE, [:table]).html_safe
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Account::RoleHelper
2
+ def role_options_for(object)
3
+ object.class.assignable_roles.map { |role| [role.id, t("#{object.class.to_s.pluralize.underscore}.fields.role_ids.options.#{role.key}.label")] }
4
+ end
5
+ end
@@ -0,0 +1,32 @@
1
+ module AttributesHelper
2
+ def current_attributes_object
3
+ @_attributes_helper_objects ? @_attributes_helper_objects.last : nil
4
+ end
5
+
6
+ def current_attributes_strategy
7
+ @_attributes_helper_strategies ? @_attributes_helper_strategies.last : nil
8
+ end
9
+
10
+ def with_attribute_settings(options)
11
+ @_attributes_helper_objects ||= []
12
+ @_attributes_helper_strategies ||= []
13
+
14
+ if options[:object]
15
+ @_attributes_helper_objects << options[:object]
16
+ end
17
+
18
+ if options[:strategy]
19
+ @_attributes_helper_strategies << options[:strategy]
20
+ end
21
+
22
+ yield
23
+
24
+ if options[:strategy]
25
+ @_attributes_helper_strategies.pop
26
+ end
27
+
28
+ if options[:object]
29
+ @_attributes_helper_objects.pop
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ module EmailHelper
2
+ def email_image_tag(image, **options)
3
+ image_underscore = image.tr("-", "_")
4
+ attachments.inline[image_underscore] = File.read(Rails.root.join("app/javascript/images/#{image}"))
5
+ image_tag attachments.inline[image_underscore].url, **options
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module ImagesHelper
2
+ def image_width_for_height(filename, target_height)
3
+ source_width, source_height = FastImage.size("#{Rails.root}/app/javascript/images/#{filename}")
4
+ ratio = source_width.to_f / source_height.to_f
5
+ (target_height * ratio).to_i
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ module Mailers::Base
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ default from: "#{I18n.t("application.name")} <#{I18n.t("application.support_email")}>"
6
+ layout "mailer"
7
+
8
+ helper :email
9
+ helper :application
10
+ helper :images
11
+ helper "account/teams"
12
+ helper "account/users"
13
+ helper "account/locale"
14
+ helper "fields/trix_editor"
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ class DeviseMailer < Devise::Mailer
2
+ def headers_for(action, opts)
3
+ headers = super(action, opts)
4
+ if resource.full_name.present?
5
+ headers[:to] = "\"#{resource.full_name}\" <#{resource.email}>"
6
+ @email = headers[:to]
7
+ end
8
+ headers
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ class UserMailer < ApplicationMailer
2
+ def welcome(user)
3
+ @user = user
4
+ @cta_url = account_dashboard_url
5
+ @values = {
6
+ # are there any substitution values you want to include?
7
+ }
8
+ mail(to: @user.email, subject: I18n.t("user_mailer.welcome.subject", **@values))
9
+ end
10
+
11
+ # technically not a 'user' email, but they'll be a user soon.
12
+ # didn't seem worth creating an entirely new mailer for.
13
+ def invited(uuid)
14
+ @invitation = Invitation.find_by_uuid uuid
15
+ return if @invitation.nil?
16
+ @cta_url = accept_account_invitation_url(@invitation.uuid)
17
+ @values = {
18
+ # Just in case the inviting user has been removed from the team...
19
+ inviter_name: @invitation.from_membership&.user&.full_name || @invitation.from_membership.name,
20
+ team_name: @invitation.team.name,
21
+ }
22
+ mail(to: @invitation.email, subject: I18n.t("user_mailer.invited.subject", **@values))
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ <h2><%= t('device.headers.resend_confirm') %></h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
4
+ <%= devise_error_messages! %>
5
+
6
+ <div class="field">
7
+ <%= f.label :email %><br />
8
+ <%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
9
+ </div>
10
+
11
+ <div class="actions">
12
+ <%= f.submit t('devise.buttons.resend_confirm') %>
13
+ </div>
14
+ <% end %>
15
+
16
+ <%= render "devise/shared/links" %>
@@ -0,0 +1,5 @@
1
+ <p><%= t('.welcome', email: @email) %></p>
2
+
3
+ <p><%= t('.description') %></p>
4
+
5
+ <p><%= link_to t('.confirm_account'), confirmation_url(@resource, confirmation_token: @token) %></p>
@@ -0,0 +1,3 @@
1
+ <p><%= t('.hello', email: @resource.email) %></p>
2
+
3
+ <p><%= t('.description') %></p>
@@ -0,0 +1,30 @@
1
+ <p><%= t('.hello', email: @resource.first_name || @resource.email) %></p>
2
+
3
+ <p><%= t('.requested_change') %></p>
4
+
5
+ <!-- Action -->
6
+ <table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
7
+ <tr>
8
+ <td align="center">
9
+ <!-- Border based button https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
10
+ <table width="100%" border="0" cellspacing="0" cellpadding="0">
11
+ <tr>
12
+ <td align="center">
13
+ <table border="0" cellspacing="0" cellpadding="0">
14
+ <tr>
15
+ <td>
16
+ <%= link_to t('.change_password'), edit_password_url(@resource, reset_password_token: @token), class: 'button button--', target: '_blank' %>
17
+ </td>
18
+ </tr>
19
+ </table>
20
+ </td>
21
+ </tr>
22
+ </table>
23
+ </td>
24
+ </tr>
25
+ </table>
26
+
27
+ <p><%= t('.ignore') %></p>
28
+ <p><%= t('.access_change') %></p>
29
+
30
+ <%= t('user_mailer.signature.html', support_email: t('application.support_email')) %>
@@ -0,0 +1,7 @@
1
+ <p><% t('.hello', email: @resource.email) %></p>
2
+
3
+ <p><%= t('.account_blocked') %></p>
4
+
5
+ <p><%= t('.click_link') %></p>
6
+
7
+ <p><%= link_to t('.unlock'), unlock_url(@resource, unlock_token: @token) %></p>
@@ -0,0 +1,17 @@
1
+ <% @title = t('devise.headers.change_password') %>
2
+ <%= render 'account/shared/workflow/box' do |p| %>
3
+ <% p.content_for :title, @title %>
4
+ <% p.content_for :body do %>
5
+ <% within_fields_namespace(:update_self) do %>
6
+ <%= form_for(resource, as: resource_name, url: password_path(resource_name), class: 'form', html: { method: :put }) do |f| %>
7
+ <%= f.hidden_field :reset_password_token %>
8
+ <%= render 'account/shared/forms/errors', form: f, attributes: [:reset_password_token] %>
9
+ <%= render 'shared/fields/password_field', form: f, method: :password, options: {autofocus: true, autocomplete: "off"} %>
10
+ <%= render 'shared/fields/password_field', form: f, method: :password_confirmation, options: {autocomplete: "off"} %>
11
+ <div class="buttons">
12
+ <%= f.submit t('devise.buttons.change_password'), class: "button" %>
13
+ </div>
14
+ <% end %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <%= render 'account/shared/workflow/box' do |p| %>
2
+ <% p.content_for :title, t('devise.titles.reset_password') %>
3
+ <% p.content_for :body do %>
4
+ <% within_fields_namespace(:sign_up) do %>
5
+ <%= form_for resource, as: resource_name, url: password_path(resource_name), html: {method: :post, class: 'form'} do |f| %>
6
+ <%= render 'account/shared/forms/errors', form: f %>
7
+
8
+ <%= render 'shared/fields/email_field', form: f, method: :email, options: {autofocus: true} do %>
9
+ <% if show_sign_up_options? %>
10
+ <% content_for :help do %>
11
+ <%= link_to t('devise.links.account'), new_user_registration_path %>
12
+ <% end %>
13
+ <% end %>
14
+ <% end %>
15
+
16
+ <%= f.submit t('devise.buttons.reset_password'), class: 'button full' %>
17
+ <% end %>
18
+ <% end %>
19
+ <% end %>
20
+ <% end %>
@@ -0,0 +1,42 @@
1
+ <%= render 'account/shared/box', divider: @backup_codes do |p| %>
2
+ <% p.content_for :title, t("users.edit.two_factor.header") %>
3
+ <% p.content_for :description, t("users.edit.two_factor.description_#{@user.otp_required_for_login? ? 'enabled' : 'disabled'}") %>
4
+ <% p.content_for :body do %>
5
+ <% if current_user.otp_required_for_login? %>
6
+ <% if @backup_codes %>
7
+
8
+ <%= render 'account/shared/alert' do %>
9
+ <%= t('users.edit.two_factor.warning').html_safe %>
10
+ <% end %>
11
+
12
+ <p><%= t('users.edit.two_factor.instructions').html_safe %></p>
13
+
14
+ <center class="py-4">
15
+ <%= current_user.otp_qr_code.as_svg(
16
+ offset: 0,
17
+ color: '000',
18
+ shape_rendering: 'crispEdges',
19
+ module_size: 4,
20
+ standalone: true
21
+ ).html_safe %>
22
+ </center>
23
+
24
+ <p><%= t('users.edit.two_factor.recovery_codes').html_safe %></p>
25
+
26
+ <center>
27
+ <% @backup_codes.each do |code| %>
28
+ <p><code><%= code %></code></p>
29
+ <% end %>
30
+ </center>
31
+
32
+ <% end %>
33
+ <% end %>
34
+ <% end %>
35
+ <% p.content_for :actions do %>
36
+ <% if current_user.otp_required_for_login? %>
37
+ <%= link_to t('users.edit.two_factor.buttons.disable'), account_two_factor_path, method: :delete, remote: true, class: "button" %>
38
+ <% else %>
39
+ <%= link_to t('users.edit.two_factor.buttons.enable'), account_two_factor_path, method: :post, remote: true, class: "button" %>
40
+ <% end %>
41
+ <% end %>
42
+ <% end %>
@@ -0,0 +1,43 @@
1
+ <h2><%= t('devise.headers.edit_registration', resource_name: resource_name.to_s.humanize) %></h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
4
+ <%= devise_error_messages! %>
5
+
6
+ <div class="field">
7
+ <%= f.label :email %><br />
8
+ <%= f.email_field :email, autofocus: true %>
9
+ </div>
10
+
11
+ <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
12
+ <div><%= t('devise.labels.wait_confirm', unconfirmed_email: resource.unconfirmed_email) %></div>
13
+ <% end %>
14
+
15
+ <div class="field">
16
+ <%= f.label :password %> <i><%= t('devise.hints.leave_blank') %></i><br />
17
+ <%= f.password_field :password, autocomplete: "off" %>
18
+ <% if @minimum_password_length %>
19
+ <br />
20
+ <em><%= t('devise.hints.password_length', length: @minimum_password_length) %></em>
21
+ <% end %>
22
+ </div>
23
+
24
+ <div class="field">
25
+ <%= f.label :password_confirmation %><br />
26
+ <%= f.password_field :password_confirmation, autocomplete: "off" %>
27
+ </div>
28
+
29
+ <div class="field">
30
+ <%= f.label :current_password %> <i><%= t('devise.hints.need_password') %></i><br />
31
+ <%= f.password_field :current_password, autocomplete: "off" %>
32
+ </div>
33
+
34
+ <div class="actions">
35
+ <%= f.submit t('global.buttons.update') %>
36
+ </div>
37
+ <% end %>
38
+
39
+ <h3><%= t('devise.headers.cancel_account') %></h3>
40
+
41
+ <p>Unhappy? <%= button_to t('devise.buttons.cancel_account'), registration_path(resource_name), data: { confirm: t('global.confirm_message') }, method: :delete %></p>
42
+
43
+ <%= link_to t('global.buttons.back'), :back %>
@@ -0,0 +1,30 @@
1
+ <%= render 'account/shared/workflow/box' do |p| %>
2
+ <% p.content_for :title, t('devise.headers.create_account') %>
3
+ <% p.content_for :body do %>
4
+ <% within_fields_namespace(:sign_up) do %>
5
+ <%= form_for resource, as: resource_name, url: registration_path(resource_name), html: {class: 'form'} do |f| %>
6
+ <%= render 'account/shared/notices' %>
7
+ <%= render 'account/shared/forms/errors', form: f %>
8
+
9
+ <%= render 'shared/fields/email_field', form: f, method: :email, options: {autofocus: true} do %>
10
+ <% content_for :help do %>
11
+ <%= link_to t('devise.links.have_account'), new_user_session_path %>
12
+ <% end %>
13
+ <% end %>
14
+
15
+ <div class="grid grid-cols-2 gap-5">
16
+ <div>
17
+ <%= render 'shared/fields/password_field', form: f, method: :password %>
18
+ </div>
19
+ <div>
20
+ <%= render 'shared/fields/password_field', form: f, method: :password_confirmation, other_options: {error: f.object.errors.full_messages_for(:password).first, hide_custom_error: true} %>
21
+ </div>
22
+ </div>
23
+
24
+ <%= f.submit t('global.buttons.sign_up'), class: 'button full' %>
25
+
26
+ <%= render 'devise/shared/oauth', verb: 'Sign Up' %>
27
+ <% end %>
28
+ <% end %>
29
+ <% end %>
30
+ <% end %>
@@ -0,0 +1,59 @@
1
+
2
+
3
+ <%= render 'account/shared/workflow/box' do |p| %>
4
+ <% p.content_for :title, t('devise.headers.sign_in') %>
5
+ <% p.content_for :body do %>
6
+ <% within_fields_namespace(:self) do %>
7
+ <%= form_for resource, as: resource_name, url: two_factor_authentication_enabled? ? users_pre_otp_path : session_path(resource_name), remote: two_factor_authentication_enabled?, html: {class: 'form'}, authenticity_token: true do |form| %>
8
+ <% with_field_settings form: form do %>
9
+ <%= render 'account/shared/notices', form: form %>
10
+ <%= render 'account/shared/forms/errors', form: form %>
11
+
12
+ <% email_field = capture do %>
13
+ <%= render 'shared/fields/email_field', method: :email, options: {autofocus: true} do %>
14
+ <% if show_sign_up_options? %>
15
+ <% content_for :help do %>
16
+ <%= link_to t('devise.links.account'), new_user_registration_path %>
17
+ <% end %>
18
+ <% end %>
19
+ <% end %>
20
+ <% end %>
21
+
22
+ <% if two_factor_authentication_enabled? %>
23
+ <div id="step-1" class="space-y">
24
+ <%= email_field %>
25
+ <%= form.submit t('global.buttons.next'), class: 'button full' %>
26
+ </div>
27
+ <% else %>
28
+ <%= email_field %>
29
+ <% end %>
30
+
31
+ <div id="step-2" class="<%= 'hidden' if two_factor_authentication_enabled? %> space-y">
32
+ <%= render 'shared/fields/password_field', method: :password do %>
33
+ <% content_for :help do %>
34
+ <%= link_to t('devise.links.forgot_password'), new_user_password_path %>
35
+ <% end %>
36
+ <% end %>
37
+
38
+ <% if two_factor_authentication_enabled? %>
39
+ <div id="step-2-otp" class="hidden">
40
+ <%= render 'shared/fields/text_field', method: :otp_attempt %>
41
+ </div>
42
+ <% end %>
43
+
44
+ <%= form.submit t('global.buttons.sign_in'), class: 'button full' %>
45
+ </div>
46
+
47
+ <div class="flex items-center">
48
+ <input id="remember_me" name="remember_me" type="checkbox" class="h-4 w-4 text-blue focus:ring-blue-dark border-gray-300 rounded">
49
+ <label for="remember_me" class="ml-2 block">
50
+ Remember me
51
+ </label>
52
+ </div>
53
+ <% end %>
54
+ <% end %>
55
+ <% end %>
56
+
57
+ <%= render 'devise/shared/oauth' %>
58
+ <% end %>
59
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <% if @email %>
2
+ $("#step-1").addClass("hidden");
3
+ $("#step-2").removeClass("hidden");
4
+ <% if @user&.otp_required_for_login %>
5
+ $("#step-2-otp").removeClass("hidden");
6
+ <% end %>
7
+ setTimeout(function() {
8
+ $("#user_password").focus();
9
+ $("#new_user").attr('action', '/users/sign_in').attr('data-remote', 'false');
10
+ }, 1);
11
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <%- if controller_name != 'sessions' %>
2
+ <%= link_to t('devise.links.log_in'), new_session_path(resource_name) %><br />
3
+ <% end -%>
4
+
5
+ <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
6
+ <%= link_to t('devise.links.sign_up'), new_registration_path(resource_name) %><br />
7
+ <% end -%>
8
+
9
+ <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
10
+ <%= link_to t('devise.links.new_confirm'), new_confirmation_path(resource_name) %><br />
11
+ <% end -%>
12
+
13
+ <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
14
+ <%= link_to t('devise.links.new_unlock'), new_unlock_path(resource_name) %><br />
15
+ <% end -%>
16
+
17
+ <%- if devise_mapping.omniauthable? %>
18
+ <%- resource_class.omniauth_providers.each do |provider| %>
19
+ <%= link_to t('devise.links.sign_in_with', provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider) %><br />
20
+ <% end -%>
21
+ <% end -%>
@@ -0,0 +1,9 @@
1
+ <% if any_oauth_enabled? %>
2
+ <% verb ||= 'Sign In' %>
3
+
4
+ <%= render 'account/shared/decision_line' %>
5
+ <div class="grid grid-cols-1 gap-2">
6
+ <%= render 'devise/shared/oauth/stripe', verb: verb if stripe_enabled? %>
7
+ <%# 🚅 super scaffolding will insert new oauth providers above this line. %>
8
+ </div>
9
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <h2><%= t('devise.headers.resend_unlock') %></h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
4
+ <%= devise_error_messages! %>
5
+
6
+ <div class="field">
7
+ <%= f.label :email %><br />
8
+ <%= f.email_field :email, autofocus: true %>
9
+ </div>
10
+
11
+ <div class="actions">
12
+ <%= f.submit t('devise.buttons.resend_unlock') %>
13
+ </div>
14
+ <% end %>
15
+
16
+ <%= render "devise/shared/links" %>
@@ -0,0 +1,110 @@
1
+ # Additional translations at https://github.com/plataformatec/devise/wiki/I18n
2
+
3
+ en:
4
+ devise:
5
+ titles:
6
+ reset_password: Reset Your Password
7
+ sign_in: Sign In
8
+ headers:
9
+ resend_confirm: Resend Confirmation Instructions
10
+ change_password: Change Your Password
11
+ reset_password: Reset Password
12
+ edit_registration: Edit %{resource_name}
13
+ cancel_account: Cancel My Account
14
+ create_account: Create Your Account
15
+ sign_in: Sign In
16
+ resend_unlock: Resend Unlock Instructions
17
+ labels:
18
+ wait_confirm: "Currently waiting confirmation for: %{unconfirmed_email}"
19
+ hints:
20
+ leave_blank: (leave blank if you don't want to change it)
21
+ length: '%{password_length} characters minimum'
22
+ need_password: (we need your current password to confirm your changes)
23
+ buttons:
24
+ resend_confirm: Resend Confirmation Instructions
25
+ change_password: Change My Password
26
+ reset_password: Reset Password by Email
27
+ cancel_account: Cancel My Account
28
+ resend_unlock: Resend Unlock Instructions
29
+ links:
30
+ account: Don't have an account?
31
+ have_account: Already have an account?
32
+ forgot_password: Forgot your password?
33
+ log_in: Log In
34
+ sign_up: Sign Up
35
+ new_confirm: Didn't receive confirmation instructions?
36
+ new_unlock: Didn't receive unlock instructions?
37
+ sign_in_with: Sign in with %{provider}
38
+
39
+ confirmations:
40
+ confirmed: "Your email address has been successfully confirmed."
41
+ send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
42
+ send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
43
+ failure:
44
+ already_authenticated: "You are already signed in."
45
+ inactive: "Your account is not activated yet."
46
+ invalid: "Invalid %{authentication_keys} or Password."
47
+ locked: "Your account is locked."
48
+ last_attempt: "You have one more attempt before your account is locked."
49
+ not_found_in_database: "Invalid %{authentication_keys} or Password."
50
+ timeout: "Your session expired. Please sign in again to continue."
51
+ unauthenticated: "You need to sign in or sign up before continuing."
52
+ unconfirmed: "You have to confirm your email address before continuing."
53
+ mailer:
54
+ confirmation_instructions:
55
+ subject: "Confirmation instructions"
56
+ welcome: Welcome %{email}!
57
+ description: 'You can confirm your account email through the link below:'
58
+ confirm_account: Confirm my account
59
+ reset_password_instructions:
60
+ subject: "Reset password instructions"
61
+ hello: Hello %{email}!
62
+ requested_change: Someone has requested a link to change your password. You can do this through the link below.
63
+ ignore: If you didn't request this, please ignore this email.
64
+ access_change: Your password won't change until you access the link above and create a new one.
65
+ change_password: Change my password
66
+ unlock_instructions:
67
+ subject: "Unlock instructions"
68
+ hello: Hello %{email}!
69
+ account_blocked: Your account has been locked due to an excessive number of unsuccessful sign in attempts.
70
+ click_link: 'Click the link below to unlock your account:'
71
+ unlock: Unlock my account
72
+ password_change:
73
+ subject: "Password Changed"
74
+ hello: Hello %{email}!
75
+ description: We're contacting you to notify you that your password has been changed.
76
+ omniauth_callbacks:
77
+ failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
78
+ success: "Successfully authenticated from %{kind} account."
79
+ passwords:
80
+ no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
81
+ send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
82
+ send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
83
+ updated: "Your password has been changed successfully. You are now signed in."
84
+ updated_not_active: "Your password has been changed successfully."
85
+ registrations:
86
+ destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
87
+ signed_up: "Welcome! You have signed up successfully."
88
+ signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
89
+ signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
90
+ signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
91
+ update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
92
+ updated: "Your account has been updated successfully."
93
+ sessions:
94
+ signed_in: "Signed in successfully."
95
+ signed_out: "Signed out successfully."
96
+ already_signed_out: "Signed out successfully."
97
+ unlocks:
98
+ send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
99
+ send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
100
+ unlocked: "Your account has been unlocked successfully. Please sign in to continue."
101
+ errors:
102
+ messages:
103
+ already_confirmed: "was already confirmed, please try signing in"
104
+ confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
105
+ expired: "has expired, please request a new one"
106
+ not_found: "not found"
107
+ not_locked: "was not locked"
108
+ not_saved:
109
+ one: "1 error prohibited this %{resource} from being saved:"
110
+ other: "%{count} errors prohibited this %{resource} from being saved:"
@@ -1,3 +1,3 @@
1
1
  module BulletTrain
2
- VERSION = "1.0.5"
2
+ VERSION = "1.0.6"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet_train
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Culver
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-24 00:00:00.000000000 Z
11
+ date: 2022-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -47,12 +47,28 @@ files:
47
47
  - app/controllers/concerns/account/onboarding/user_email/controller_base.rb
48
48
  - app/controllers/concerns/account/teams/controller_base.rb
49
49
  - app/controllers/concerns/account/users/controller_base.rb
50
+ - app/controllers/concerns/registrations/controller_base.rb
51
+ - app/controllers/concerns/sessions/controller_base.rb
52
+ - app/controllers/registrations_controller.rb
53
+ - app/controllers/sessions_controller.rb
54
+ - app/helpers/account/buttons_helper.rb
55
+ - app/helpers/account/dates_helper.rb
56
+ - app/helpers/account/forms_helper.rb
50
57
  - app/helpers/account/invitations_helper.rb
58
+ - app/helpers/account/locale_helper.rb
59
+ - app/helpers/account/markdown_helper.rb
51
60
  - app/helpers/account/memberships_helper.rb
61
+ - app/helpers/account/role_helper.rb
52
62
  - app/helpers/account/teams_helper.rb
53
63
  - app/helpers/account/users_helper.rb
64
+ - app/helpers/attributes_helper.rb
65
+ - app/helpers/email_helper.rb
66
+ - app/helpers/images_helper.rb
54
67
  - app/helpers/invitation_only_helper.rb
55
68
  - app/helpers/invitations_helper.rb
69
+ - app/mailers/concerns/mailers/base.rb
70
+ - app/mailers/devise_mailer.rb
71
+ - app/mailers/user_mailer.rb
56
72
  - app/models/concerns/invitations/base.rb
57
73
  - app/models/concerns/memberships/base.rb
58
74
  - app/models/concerns/teams/base.rb
@@ -97,6 +113,22 @@ files:
97
113
  - app/views/account/users/_form.html.erb
98
114
  - app/views/account/users/edit.html.erb
99
115
  - app/views/account/users/show.html.erb
116
+ - app/views/devise/confirmations/new.html.erb
117
+ - app/views/devise/mailer/confirmation_instructions.html.erb
118
+ - app/views/devise/mailer/password_change.html.erb
119
+ - app/views/devise/mailer/reset_password_instructions.html.erb
120
+ - app/views/devise/mailer/unlock_instructions.html.erb
121
+ - app/views/devise/passwords/edit.html.erb
122
+ - app/views/devise/passwords/new.html.erb
123
+ - app/views/devise/registrations/_two_factor.html.erb
124
+ - app/views/devise/registrations/edit.html.erb
125
+ - app/views/devise/registrations/new.html.erb
126
+ - app/views/devise/sessions/new.html.erb
127
+ - app/views/devise/sessions/pre_otp.js.erb
128
+ - app/views/devise/shared/_links.html.erb
129
+ - app/views/devise/shared/_oauth.html.erb
130
+ - app/views/devise/unlocks/new.html.erb
131
+ - config/locales/en/devise.en.yml
100
132
  - config/locales/en/invitations.en.yml
101
133
  - config/locales/en/memberships.en.yml
102
134
  - config/locales/en/onboarding/user_details.en.yml