bullet_train 1.0.3 → 1.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/account/invitations_controller.rb +1 -144
- data/app/controllers/account/memberships_controller.rb +1 -130
- data/app/controllers/account/onboarding/user_details_controller.rb +1 -61
- data/app/controllers/account/onboarding/user_email_controller.rb +1 -63
- data/app/controllers/account/teams_controller.rb +1 -120
- data/app/controllers/account/users_controller.rb +1 -57
- data/app/controllers/concerns/account/invitations/controller_base.rb +150 -0
- data/app/controllers/concerns/account/memberships/controller_base.rb +136 -0
- data/app/controllers/concerns/account/onboarding/user_details/controller_base.rb +67 -0
- data/app/controllers/concerns/account/onboarding/user_email/controller_base.rb +69 -0
- data/app/controllers/concerns/account/teams/controller_base.rb +126 -0
- data/app/controllers/concerns/account/users/controller_base.rb +63 -0
- data/app/controllers/concerns/registrations/controller_base.rb +39 -0
- data/app/controllers/concerns/sessions/controller_base.rb +15 -0
- data/app/controllers/registrations_controller.rb +9 -0
- data/app/controllers/sessions_controller.rb +3 -0
- data/app/helpers/account/buttons_helper.rb +12 -0
- data/app/helpers/account/dates_helper.rb +38 -0
- data/app/helpers/account/forms_helper.rb +67 -0
- data/app/helpers/account/locale_helper.rb +51 -0
- data/app/helpers/account/markdown_helper.rb +5 -0
- data/app/helpers/account/role_helper.rb +5 -0
- data/app/helpers/attributes_helper.rb +32 -0
- data/app/helpers/email_helper.rb +7 -0
- data/app/helpers/images_helper.rb +7 -0
- data/app/mailers/concerns/mailers/base.rb +16 -0
- data/app/mailers/devise_mailer.rb +10 -0
- data/app/mailers/user_mailer.rb +24 -0
- data/app/models/concerns/invitations/{core.rb → base.rb} +1 -1
- data/app/models/concerns/memberships/{core.rb → base.rb} +1 -1
- data/app/models/concerns/teams/{core.rb → base.rb} +1 -1
- data/app/models/concerns/users/{core.rb → base.rb} +1 -1
- data/app/views/devise/confirmations/new.html.erb +16 -0
- data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise/mailer/password_change.html.erb +3 -0
- data/app/views/devise/mailer/reset_password_instructions.html.erb +30 -0
- data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/devise/passwords/edit.html.erb +17 -0
- data/app/views/devise/passwords/new.html.erb +20 -0
- data/app/views/devise/registrations/_two_factor.html.erb +42 -0
- data/app/views/devise/registrations/edit.html.erb +43 -0
- data/app/views/devise/registrations/new.html.erb +30 -0
- data/app/views/devise/sessions/new.html.erb +59 -0
- data/app/views/devise/sessions/pre_otp.js.erb +11 -0
- data/app/views/devise/shared/_links.html.erb +21 -0
- data/app/views/devise/shared/_oauth.html.erb +9 -0
- data/app/views/devise/unlocks/new.html.erb +16 -0
- data/config/locales/en/devise.en.yml +110 -0
- data/config/routes.rb +40 -0
- data/lib/bullet_train/version.rb +1 -1
- metadata +44 -6
@@ -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,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,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,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,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,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 %>
|