bullet_train 1.2.26 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/concerns/account/controllers/base.rb +2 -0
- data/app/controllers/concerns/account/memberships/controller_base.rb +2 -1
- data/app/controllers/concerns/account/teams/controller_base.rb +1 -1
- data/app/controllers/concerns/sessions/controller_base.rb +35 -0
- data/app/controllers/sessions_controller.rb +0 -32
- data/app/helpers/account/dates_helper.rb +11 -31
- data/app/helpers/account/markdown_helper.rb +8 -1
- data/app/helpers/account/users_helper.rb +43 -24
- data/app/helpers/attributes_helper.rb +7 -22
- data/app/helpers/invitation_only_helper.rb +9 -1
- data/app/javascript/controllers/bulk_actions_controller.js +1 -0
- data/app/models/billing/mock_limiter.rb +1 -1
- data/app/models/concerns/memberships/base.rb +17 -0
- data/app/models/concerns/records/base.rb +3 -1
- data/app/models/concerns/teams/base.rb +4 -8
- data/app/models/concerns/users/base.rb +15 -1
- data/app/views/account/memberships/_index.html.erb +1 -1
- data/app/views/account/onboarding/user_details/edit.html.erb +1 -0
- data/app/views/account/users/_form.html.erb +6 -4
- data/app/views/devise/registrations/new.html.erb +1 -1
- data/app/views/layouts/docs.html.erb +15 -3
- data/app/views/layouts/public.html.erb +31 -0
- data/config/locales/en/base.yml +9 -0
- data/config/locales/en/memberships.en.yml +3 -0
- data/config/locales/en/users.en.yml +5 -0
- data/docs/application-hash.md +25 -0
- data/docs/billing/stripe.md +3 -3
- data/docs/billing/usage.md +7 -7
- data/docs/field-partials/buttons.md +4 -4
- data/docs/field-partials/date-related-fields.md +13 -0
- data/docs/field-partials/file-field.md +1 -2
- data/docs/field-partials/super-select.md +23 -4
- data/docs/field-partials.md +24 -11
- data/docs/font-awesome-pro.md +1 -1
- data/docs/index.md +9 -8
- data/docs/indirection.md +5 -1
- data/docs/overriding.md +1 -1
- data/docs/seeds.md +3 -3
- data/docs/super-scaffolding/delegated-types.md +27 -24
- data/docs/super-scaffolding/options.md +24 -0
- data/docs/super-scaffolding/sortable.md +1 -1
- data/docs/super-scaffolding.md +7 -6
- data/docs/testing.md +1 -1
- data/docs/themes.md +4 -4
- data/docs/tunneling.md +2 -2
- data/docs/upgrades.md +7 -7
- data/docs/zapier.md +1 -1
- data/lib/bullet_train/configuration.rb +9 -3
- data/lib/bullet_train/resolver.rb +11 -6
- data/lib/bullet_train/version.rb +1 -1
- data/lib/bullet_train.rb +13 -8
- data/lib/tasks/bullet_train_tasks.rake +30 -0
- metadata +23 -6
- data/README.md +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67f18258ccc08e8f9c7ab4febd061776dbd014c14a851ff53a24b08b8f52740a
|
4
|
+
data.tar.gz: d4a1eaccc46bdc38aac7ea2996f9908b899ddd7310e041a566015a59ff36e50e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 255cfc9f4e0abcf1c25ff7d1e562f0a7c9bce5d11eefa1847feaae5247b04fa0a48679733d382216c0081cda8add87963a8a5c616cf438264cec1e7e0a316247
|
7
|
+
data.tar.gz: b777a4c8d8f978ed1037c433b976351793dfadfdb59dfde8f23d4fdec72031d1dea52c9e36e664f2f560c9617fe8382017ff3ec5f9760b1afb17e77cea332297
|
@@ -110,7 +110,8 @@ module Account::Memberships::ControllerBase
|
|
110
110
|
strong_params = params.require(:membership).permit(
|
111
111
|
:user_first_name,
|
112
112
|
:user_last_name,
|
113
|
-
:user_profile_photo_id,
|
113
|
+
:user_profile_photo_id, # For Cloudinary
|
114
|
+
:user_profile_photo, # For ActiveStorage
|
114
115
|
*permitted_fields,
|
115
116
|
*permitted_arrays,
|
116
117
|
)
|
@@ -1,6 +1,41 @@
|
|
1
1
|
module Sessions::ControllerBase
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
+
# If user_return_to points to an oauth path we disable Turbo on the sign in form.
|
5
|
+
# This makes it work when we need to redirect to external sites and/or custom protocols.
|
6
|
+
# With Turbo enabled the browser will block those redirects with a CORS error.
|
7
|
+
# https://github.com/bullet-train-co/bullet_train/issues/384
|
8
|
+
def user_return_to_is_oauth
|
9
|
+
session["user_return_to"]&.match(/^\/oauth/)
|
10
|
+
end
|
11
|
+
|
12
|
+
included do
|
13
|
+
helper_method :user_return_to_is_oauth
|
14
|
+
end
|
15
|
+
|
16
|
+
def new
|
17
|
+
# We allow people to pass in a URL to redirect to after sign in is complete. We have to do this because Safari
|
18
|
+
# doesn't allow them to set this in a session before a redirect if there isn't already a session. However, for
|
19
|
+
# security reasons we have to make sure we control the URL where we will redirect to, otherwise people could
|
20
|
+
# trick folks into redirecting to a fake destination in a phishing scheme.
|
21
|
+
if params[:return_url]&.start_with?(ENV["BASE_URL"])
|
22
|
+
store_location_for(resource_name, params[:return_url])
|
23
|
+
end
|
24
|
+
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def destroy
|
29
|
+
if params.include?(:onboard_logout)
|
30
|
+
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
|
31
|
+
set_flash_message! :notice, :signed_out if signed_out
|
32
|
+
yield if block_given?
|
33
|
+
redirect_to root_path
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
4
39
|
def pre_otp
|
5
40
|
if (@email = params["user"]["email"].downcase.strip.presence)
|
6
41
|
@user = User.find_by(email: @email)
|
@@ -1,35 +1,3 @@
|
|
1
1
|
class SessionsController < Devise::SessionsController
|
2
2
|
include Sessions::ControllerBase
|
3
|
-
|
4
|
-
# If user_return_to points to an oauth path we disable Turbo on the sign in form.
|
5
|
-
# This makes it work when we need to redirect to external sites and/or custom protocols.
|
6
|
-
# With Turbo enabled the browser will block those redirects with a CORS error.
|
7
|
-
# https://github.com/bullet-train-co/bullet_train/issues/384
|
8
|
-
def user_return_to_is_oauth
|
9
|
-
session["user_return_to"]&.match(/^\/oauth/)
|
10
|
-
end
|
11
|
-
helper_method :user_return_to_is_oauth
|
12
|
-
|
13
|
-
def new
|
14
|
-
# We allow people to pass in a URL to redirect to after sign in is complete. We have to do this because Safari
|
15
|
-
# doesn't allow them to set this in a session before a redirect if there isn't already a session. However, for
|
16
|
-
# security reasons we have to make sure we control the URL where we will redirect to, otherwise people could
|
17
|
-
# trick folks into redirecting to a fake destination in a phishing scheme.
|
18
|
-
if params[:return_url]&.start_with?(ENV["BASE_URL"])
|
19
|
-
store_location_for(resource_name, params[:return_url])
|
20
|
-
end
|
21
|
-
|
22
|
-
super
|
23
|
-
end
|
24
|
-
|
25
|
-
def destroy
|
26
|
-
if params.include?(:onboard_logout)
|
27
|
-
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
|
28
|
-
set_flash_message! :notice, :signed_out if signed_out
|
29
|
-
yield if block_given?
|
30
|
-
redirect_to root_path
|
31
|
-
else
|
32
|
-
super
|
33
|
-
end
|
34
|
-
end
|
35
3
|
end
|
@@ -1,40 +1,20 @@
|
|
1
1
|
module Account::DatesHelper
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
if custom_date_format
|
6
|
-
local_time(timestamp).strftime(custom_date_format)
|
7
|
-
elsif local_time(timestamp).year == local_time(Time.now).year
|
8
|
-
local_time(timestamp).strftime("%B %-d")
|
9
|
-
else
|
10
|
-
local_time(timestamp).strftime("%B %-d, %Y")
|
11
|
-
end
|
2
|
+
def display_date(timestamp, format: :default, date_format: nil)
|
3
|
+
format = date_format if date_format
|
4
|
+
localize(local_time(timestamp).to_date, format: format) if timestamp
|
12
5
|
end
|
13
6
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def display_date_and_time(timestamp, custom_date_format = nil, custom_time_format = nil)
|
18
|
-
return nil unless timestamp
|
19
|
-
|
20
|
-
# today?
|
21
|
-
if local_time(timestamp).to_date == local_time(Time.now).to_date
|
22
|
-
"Today at #{display_time(timestamp, custom_time_format)}"
|
23
|
-
# yesterday?
|
24
|
-
elsif (local_time(timestamp).to_date) == (local_time(Time.now).to_date - 1.day)
|
25
|
-
"Yesterday at #{display_time(timestamp, custom_time_format)}"
|
26
|
-
else
|
27
|
-
"#{display_date(timestamp, custom_date_format)} at #{display_time(timestamp, custom_time_format)}"
|
28
|
-
end
|
7
|
+
def display_time(timestamp, format: :default, time_format: nil)
|
8
|
+
format = time_format if time_format
|
9
|
+
localize(local_time(timestamp).to_time, format: format) if timestamp
|
29
10
|
end
|
30
11
|
|
31
|
-
|
32
|
-
|
33
|
-
local_time(timestamp).
|
12
|
+
def display_date_and_time(timestamp, format: :default, date_format: nil, time_format: nil)
|
13
|
+
format = "#{date_format} #{time_format}" if date_format && time_format
|
14
|
+
localize(local_time(timestamp).to_datetime, format: format) if timestamp
|
34
15
|
end
|
35
16
|
|
36
|
-
def local_time(
|
37
|
-
|
38
|
-
time.in_time_zone(current_user.time_zone)
|
17
|
+
def local_time(timestamp)
|
18
|
+
timestamp&.in_time_zone(current_user.time_zone)
|
39
19
|
end
|
40
20
|
end
|
@@ -1,5 +1,12 @@
|
|
1
1
|
module Account::MarkdownHelper
|
2
2
|
def markdown(string)
|
3
|
-
|
3
|
+
if defined?(Commonmarker.to_html)
|
4
|
+
Commonmarker.to_html(string, options: {
|
5
|
+
plugins: {syntax_highlighter: {theme: "InspiredGitHub"}},
|
6
|
+
render: {width: 120, unsafe: true}
|
7
|
+
}).html_safe
|
8
|
+
else
|
9
|
+
CommonMarker.render_html(string, :UNSAFE, [:table]).html_safe
|
10
|
+
end
|
4
11
|
end
|
5
12
|
end
|
@@ -1,23 +1,20 @@
|
|
1
1
|
module Account::UsersHelper
|
2
|
-
def profile_photo_for(url: nil, email: nil, first_name: nil, last_name: nil)
|
2
|
+
def profile_photo_for(url: nil, email: nil, first_name: nil, last_name: nil, profile_header: false)
|
3
|
+
size_details = profile_header ? {width: 700, height: 200} : {width: 100, height: 100}
|
4
|
+
size_details[:crop] = :fill
|
5
|
+
|
3
6
|
if cloudinary_enabled? && !url.blank?
|
4
|
-
cl_image_path(url, width
|
7
|
+
cl_image_path(url, size_details[:width], size_details[:height], size_details[:crop])
|
8
|
+
elsif !url.blank?
|
9
|
+
url + "?" + size_details.to_param
|
5
10
|
else
|
6
|
-
|
7
|
-
"https://ui-avatars.com/api/?" + {
|
8
|
-
color: "ffffff",
|
9
|
-
background: background_color,
|
10
|
-
bold: true,
|
11
|
-
# email.to_s should not be necessary once we fix the edge case of cancelling an unclaimed membership
|
12
|
-
name: [first_name, last_name].join(" ").strip.presence || email,
|
13
|
-
size: 200,
|
14
|
-
}.to_param
|
11
|
+
ui_avatar_params(email, first_name, last_name)
|
15
12
|
end
|
16
13
|
end
|
17
14
|
|
18
15
|
def user_profile_photo_url(user)
|
19
16
|
profile_photo_for(
|
20
|
-
url: user
|
17
|
+
url: get_photo_url_from(user),
|
21
18
|
email: user.email,
|
22
19
|
first_name: user.first_name,
|
23
20
|
last_name: user.last_name
|
@@ -29,7 +26,7 @@ module Account::UsersHelper
|
|
29
26
|
user_profile_photo_url(membership.user)
|
30
27
|
else
|
31
28
|
profile_photo_for(
|
32
|
-
url: membership
|
29
|
+
url: get_photo_url_from(membership),
|
33
30
|
email: membership.invitation&.email || membership.user_email,
|
34
31
|
first_name: membership.user_first_name,
|
35
32
|
last_name: membership.user_last_name
|
@@ -37,25 +34,21 @@ module Account::UsersHelper
|
|
37
34
|
end
|
38
35
|
end
|
39
36
|
|
37
|
+
# TODO: We can do away with these three `profile_header` methods, I'm just
|
38
|
+
# leaving them in case we have other developers depending on these methods.
|
40
39
|
def profile_header_photo_for(url: nil, email: nil, first_name: nil, last_name: nil)
|
41
40
|
if cloudinary_enabled? && !url.blank?
|
42
41
|
cl_image_path(url, width: 700, height: 200, crop: :fill)
|
42
|
+
elsif !url.blank?
|
43
|
+
url + "?" + {size: 200}.to_param
|
43
44
|
else
|
44
|
-
|
45
|
-
"https://ui-avatars.com/api/?" + {
|
46
|
-
color: "ffffff",
|
47
|
-
background: background_color,
|
48
|
-
bold: true,
|
49
|
-
# email.to_s should not be necessary once we fix the edge case of cancelling an unclaimed membership
|
50
|
-
name: "#{first_name&.first || email.to_s[0]} #{last_name&.first || email.to_s[1]}",
|
51
|
-
size: 200,
|
52
|
-
}.to_param
|
45
|
+
ui_avatar_params(email, first_name, last_name)
|
53
46
|
end
|
54
47
|
end
|
55
48
|
|
56
49
|
def user_profile_header_photo_url(user)
|
57
50
|
profile_header_photo_for(
|
58
|
-
url: user
|
51
|
+
url: get_photo_url_from(user),
|
59
52
|
email: user.email,
|
60
53
|
first_name: user.first_name,
|
61
54
|
last_name: user.last_name
|
@@ -67,7 +60,7 @@ module Account::UsersHelper
|
|
67
60
|
user_profile_header_photo_url(membership.user)
|
68
61
|
else
|
69
62
|
profile_header_photo_for(
|
70
|
-
url: membership
|
63
|
+
url: get_photo_url_from(membership),
|
71
64
|
email: membership.invitation&.email || membership.user_email,
|
72
65
|
first_name: membership.user_first_name,
|
73
66
|
last_name: membership.user&.last_name || membership.user_last_name
|
@@ -75,6 +68,32 @@ module Account::UsersHelper
|
|
75
68
|
end
|
76
69
|
end
|
77
70
|
|
71
|
+
def get_photo_url_from(resource)
|
72
|
+
photo_method = if resource.is_a?(User)
|
73
|
+
:profile_photo
|
74
|
+
elsif resource.is_a?(Membership)
|
75
|
+
:user_profile_photo
|
76
|
+
end
|
77
|
+
|
78
|
+
if cloudinary_enabled?
|
79
|
+
resource.send("#{photo_method}_id".to_sym)
|
80
|
+
elsif resource.send(photo_method).attached?
|
81
|
+
url_for(resource.send(photo_method))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def ui_avatar_params(email, first_name, last_name)
|
86
|
+
background_color = Colorizer.colorize_similarly(email.to_s, 0.5, 0.6).delete("#")
|
87
|
+
"https://ui-avatars.com/api/?" + {
|
88
|
+
color: "ffffff",
|
89
|
+
background: background_color,
|
90
|
+
bold: true,
|
91
|
+
# email.to_s should not be necessary once we fix the edge case of cancelling an unclaimed membership
|
92
|
+
name: "#{first_name&.first || email.to_s[0]} #{last_name&.first || email.to_s[1]}",
|
93
|
+
size: 200,
|
94
|
+
}.to_param
|
95
|
+
end
|
96
|
+
|
78
97
|
def current_membership
|
79
98
|
current_user.memberships.where(team: current_team).first
|
80
99
|
end
|
@@ -1,32 +1,17 @@
|
|
1
1
|
module AttributesHelper
|
2
2
|
def current_attributes_object
|
3
|
-
@
|
3
|
+
@_current_attribute_settings&.dig(:object)
|
4
4
|
end
|
5
5
|
|
6
6
|
def current_attributes_strategy
|
7
|
-
@
|
7
|
+
@_current_attributes_settings&.dig(:strategy)
|
8
8
|
end
|
9
9
|
|
10
|
-
def with_attribute_settings(
|
11
|
-
|
12
|
-
@
|
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
|
-
|
10
|
+
def with_attribute_settings(object: current_attributes_object, strategy: current_attributes_strategy)
|
11
|
+
old_attribute_settings = @_current_attribute_settings
|
12
|
+
@_current_attribute_settings = {object: object, strategy: strategy}
|
22
13
|
yield
|
23
|
-
|
24
|
-
|
25
|
-
@_attributes_helper_strategies.pop
|
26
|
-
end
|
27
|
-
|
28
|
-
if options[:object]
|
29
|
-
@_attributes_helper_objects.pop
|
30
|
-
end
|
14
|
+
ensure
|
15
|
+
@_current_attribute_settings = old_attribute_settings
|
31
16
|
end
|
32
17
|
end
|
@@ -1,6 +1,14 @@
|
|
1
|
+
require "active_support/security_utils"
|
2
|
+
|
1
3
|
module InvitationOnlyHelper
|
2
4
|
def invited?
|
3
|
-
session[:invitation_key].present?
|
5
|
+
return false unless session[:invitation_key].present?
|
6
|
+
|
7
|
+
result = invitation_keys.find do |key|
|
8
|
+
ActiveSupport::SecurityUtils.secure_compare(key, session[:invitation_key])
|
9
|
+
end
|
10
|
+
|
11
|
+
result.present?
|
4
12
|
end
|
5
13
|
|
6
14
|
def show_sign_up_options?
|
@@ -2,6 +2,8 @@ module Memberships::Base
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
+
attr_accessor :user_profile_photo_removal
|
6
|
+
|
5
7
|
# See `docs/permissions.md` for details.
|
6
8
|
include Roles::Support
|
7
9
|
|
@@ -16,6 +18,9 @@ module Memberships::Base
|
|
16
18
|
|
17
19
|
has_many :scaffolding_absolutely_abstract_creative_concepts_collaborators, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcepts::Collaborator", dependent: :destroy
|
18
20
|
|
21
|
+
# Image uploading
|
22
|
+
has_one_attached :user_profile_photo
|
23
|
+
|
19
24
|
after_destroy do
|
20
25
|
# if we're destroying a user's membership to the team they have set as
|
21
26
|
# current, then we need to remove that so they don't get an error.
|
@@ -25,6 +30,8 @@ module Memberships::Base
|
|
25
30
|
end
|
26
31
|
end
|
27
32
|
|
33
|
+
after_validation :remove_user_profile_photo, if: :user_profile_photo_removal?
|
34
|
+
|
28
35
|
scope :excluding_platform_agents, -> { where(platform_agent_of: nil) }
|
29
36
|
scope :platform_agents, -> { where.not(platform_agent_of: nil) }
|
30
37
|
scope :current_and_invited, -> { includes(:invitation).where("user_id IS NOT NULL OR invitations.id IS NOT NULL").references(:invitation) }
|
@@ -140,4 +147,14 @@ module Memberships::Base
|
|
140
147
|
def should_receive_notifications?
|
141
148
|
invitation.present? || user.present?
|
142
149
|
end
|
150
|
+
|
151
|
+
def user_profile_photo_removal?
|
152
|
+
user_profile_photo_removal.present?
|
153
|
+
end
|
154
|
+
|
155
|
+
def remove_user_profile_photo
|
156
|
+
user_profile_photo.purge
|
157
|
+
end
|
158
|
+
|
159
|
+
ActiveSupport.run_load_hooks :bullet_train_memberships_base, self
|
143
160
|
end
|
@@ -21,7 +21,7 @@ module Records::Base
|
|
21
21
|
end
|
22
22
|
|
23
23
|
include CableReady::Updatable
|
24
|
-
|
24
|
+
enable_cable_ready_updates
|
25
25
|
|
26
26
|
extend ActiveHash::Associations::ActiveRecordExtensions
|
27
27
|
|
@@ -91,4 +91,6 @@ module Records::Base
|
|
91
91
|
end.attributes!
|
92
92
|
end
|
93
93
|
end
|
94
|
+
|
95
|
+
ActiveSupport.run_load_hooks :bullet_train_records_base, self
|
94
96
|
end
|
@@ -4,7 +4,7 @@ module Teams::Base
|
|
4
4
|
included do
|
5
5
|
# super scaffolding
|
6
6
|
unless scaffolding_things_disabled?
|
7
|
-
has_many :scaffolding_absolutely_abstract_creative_concepts, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcept", dependent: :destroy,
|
7
|
+
has_many :scaffolding_absolutely_abstract_creative_concepts, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcept", dependent: :destroy, enable_cable_ready_updates: true
|
8
8
|
end
|
9
9
|
|
10
10
|
# memberships and invitations
|
@@ -27,10 +27,6 @@ module Teams::Base
|
|
27
27
|
if defined?(Billing::Stripe::Subscription)
|
28
28
|
has_many :billing_stripe_subscriptions, class_name: "Billing::Stripe::Subscription", dependent: :destroy, foreign_key: :team_id
|
29
29
|
end
|
30
|
-
|
31
|
-
if defined?(Billing::Usage::TeamSupport)
|
32
|
-
include Billing::Usage::TeamSupport
|
33
|
-
end
|
34
30
|
end
|
35
31
|
|
36
32
|
# validations
|
@@ -39,9 +35,7 @@ module Teams::Base
|
|
39
35
|
end
|
40
36
|
|
41
37
|
def platform_agent_access_tokens
|
42
|
-
|
43
|
-
platform_agent_user_ids = memberships.platform_agents.map(&:user_id).compact
|
44
|
-
Platform::AccessToken.joins(:application).where(resource_owner_id: platform_agent_user_ids, application: {team: nil})
|
38
|
+
Platform::AccessToken.joins(:application).where(resource_owner_id: users.where.not(platform_agent_of_id: nil), application: {team: nil})
|
45
39
|
end
|
46
40
|
|
47
41
|
def admins
|
@@ -83,4 +77,6 @@ module Teams::Base
|
|
83
77
|
billing_subscriptions.active.empty?
|
84
78
|
end
|
85
79
|
end
|
80
|
+
|
81
|
+
ActiveSupport.run_load_hooks :bullet_train_teams_base, self
|
86
82
|
end
|
@@ -2,6 +2,8 @@ module Users::Base
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
+
attr_accessor :profile_photo_removal
|
6
|
+
|
5
7
|
if two_factor_authentication_enabled?
|
6
8
|
devise :two_factor_authenticatable, :two_factor_backupable
|
7
9
|
else
|
@@ -9,7 +11,7 @@ module Users::Base
|
|
9
11
|
end
|
10
12
|
|
11
13
|
devise :omniauthable
|
12
|
-
devise :pwned_password if BulletTrain::Configuration.
|
14
|
+
devise :pwned_password if BulletTrain::Configuration.strong_passwords
|
13
15
|
devise :registerable
|
14
16
|
devise :recoverable
|
15
17
|
devise :rememberable
|
@@ -27,6 +29,9 @@ module Users::Base
|
|
27
29
|
# oauth providers
|
28
30
|
has_many :oauth_stripe_accounts, class_name: "Oauth::StripeAccount" if stripe_enabled?
|
29
31
|
|
32
|
+
# Image uploading
|
33
|
+
has_one_attached :profile_photo
|
34
|
+
|
30
35
|
# platform functionality.
|
31
36
|
belongs_to :platform_agent_of, class_name: "Platform::Application", optional: true
|
32
37
|
|
@@ -35,6 +40,7 @@ module Users::Base
|
|
35
40
|
validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
|
36
41
|
|
37
42
|
# callbacks
|
43
|
+
after_validation :remove_profile_photo, if: :profile_photo_removal?
|
38
44
|
after_update :set_teams_time_zone
|
39
45
|
end
|
40
46
|
|
@@ -166,4 +172,12 @@ module Users::Base
|
|
166
172
|
team.update(time_zone: time_zone) if team.users.count == 1
|
167
173
|
end
|
168
174
|
end
|
175
|
+
|
176
|
+
def profile_photo_removal?
|
177
|
+
profile_photo_removal.present?
|
178
|
+
end
|
179
|
+
|
180
|
+
def remove_profile_photo
|
181
|
+
profile_photo.purge
|
182
|
+
end
|
169
183
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<% hide_actions ||= false %>
|
3
3
|
<% hide_back ||= false %>
|
4
4
|
|
5
|
-
<%=
|
5
|
+
<%= cable_ready_updates_for context, :memberships do %>
|
6
6
|
<%= render 'account/shared/box' do |box| %>
|
7
7
|
<% box.title t(".contexts.#{context.class.name.underscore}.header") %>
|
8
8
|
<% box.description do %>
|
@@ -6,6 +6,7 @@
|
|
6
6
|
<% within_fields_namespace(:self) do %>
|
7
7
|
<%= form_for @user, url: account_onboarding_user_detail_path(@user), method: :put, html: {class: 'form'} do |f| %>
|
8
8
|
<%= render 'account/shared/forms/errors', form: f %>
|
9
|
+
<%= render 'account/shared/notices', form: f %>
|
9
10
|
|
10
11
|
<div class="grid grid-cols-1 gap-y gap-x sm:grid-cols-2">
|
11
12
|
<div class="sm:col-span-1">
|
@@ -13,11 +13,13 @@
|
|
13
13
|
<%= render 'shared/fields/text_field', method: :last_name %>
|
14
14
|
</div>
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
<div class="sm:col-span-2">
|
17
|
+
<% if cloudinary_enabled? %>
|
18
18
|
<%= render 'shared/fields/cloudinary_image', method: :profile_photo_id %>
|
19
|
-
|
20
|
-
|
19
|
+
<% else %>
|
20
|
+
<%= render 'shared/fields/file_field', method: :profile_photo %>
|
21
|
+
<% end %>
|
22
|
+
</div>
|
21
23
|
|
22
24
|
<div class="sm:col-span-2">
|
23
25
|
<%= render 'shared/fields/super_select', method: :time_zone,
|
@@ -109,7 +109,7 @@
|
|
109
109
|
<% end %>
|
110
110
|
<% end %>
|
111
111
|
|
112
|
-
<%= render 'account/shared/menu/item', url: '/docs/i18n', label: '
|
112
|
+
<%= render 'account/shared/menu/item', url: '/docs/i18n', label: 'Internationalization' do |p| %>
|
113
113
|
<% p.icon do %>
|
114
114
|
<i class="fa-brands fa-js ti ti-world"></i>
|
115
115
|
<% end %>
|
@@ -152,6 +152,12 @@
|
|
152
152
|
<i class="fal fa-gear ti ti-settings"></i>
|
153
153
|
<% end %>
|
154
154
|
<% end %>
|
155
|
+
|
156
|
+
<%= render 'account/shared/menu/item', url: '/docs/application-hash.md', label: 'Application Hash' do |p| %>
|
157
|
+
<% p.content_for :icon do %>
|
158
|
+
<i class="fal fa-brackets-curly ti ti-view-list-alt"></i>
|
159
|
+
<% end %>
|
160
|
+
<% end %>
|
155
161
|
<% end %>
|
156
162
|
|
157
163
|
<%= render 'account/shared/menu/section', title: 'Accounts & Teams' do %>
|
@@ -204,6 +210,12 @@
|
|
204
210
|
<i class="fal fa-swatchbook ti ti-widget"></i>
|
205
211
|
<% end %>
|
206
212
|
<% end %>
|
213
|
+
|
214
|
+
<%= render 'account/shared/menu/item', url: 'https://github.com/bullet-train-co/showcase', label: 'Showcase' do |p| %>
|
215
|
+
<% p.icon do %>
|
216
|
+
<i class="fal fa-swatchbook ti ti-panel"></i>
|
217
|
+
<% end %>
|
218
|
+
<% end %>
|
207
219
|
<% end %>
|
208
220
|
|
209
221
|
<%= render 'account/shared/menu/section', title: 'Billing' do %>
|
@@ -292,7 +304,7 @@
|
|
292
304
|
<button
|
293
305
|
data-mobile-menu-target="revealable"
|
294
306
|
data-action="mobile-menu#close"
|
295
|
-
|
307
|
+
|
296
308
|
data-transition-enter="transition-opacity ease-linear duration-200"
|
297
309
|
data-transition-enter-start="opacity-0"
|
298
310
|
data-transition-enter-end="opacity-100"
|
@@ -306,7 +318,7 @@
|
|
306
318
|
</button>
|
307
319
|
<div
|
308
320
|
data-mobile-menu-target="revealable"
|
309
|
-
|
321
|
+
|
310
322
|
data-transition-enter="transition ease-in-out duration-200 transform"
|
311
323
|
data-transition-enter-start="-translate-x-full"
|
312
324
|
data-transition-enter-end="translate-x-0"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html class="theme-<%= BulletTrain::Themes::Light.color %> <%= "theme-secondary-#{BulletTrain::Themes::Light.secondary_color}" if BulletTrain::Themes::Light.secondary_color %>">
|
3
|
+
<head>
|
4
|
+
<%= render 'shared/layouts/head' %>
|
5
|
+
</head>
|
6
|
+
|
7
|
+
<body class="min-h-screen <%= BulletTrain::Themes::Light.background || "bg-gradient-to-br from-secondary-200 to-primary-400 dark:from-primary-900 dark:to-primary-600" %> text-slate-700 text-sm font-normal dark:text-slate-300">
|
8
|
+
<div class="md:p-5 main-container-padding">
|
9
|
+
<div class="h-screen md:h-auto md:rounded-lg flex shadow main-container">
|
10
|
+
|
11
|
+
<% if BulletTrain::Themes::Light.navigation == :left %>
|
12
|
+
<div class="hidden lg:flex lg:flex-shrink-0 bg-gradient-to-b from-primary-700 to-primary-800 dark:from-slate-800 dark:to-slate-800 md:rounded-l-lg">
|
13
|
+
<div class="w-64">
|
14
|
+
<%= render "account/shared/menu/sidebar" %>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
<% end %>
|
18
|
+
|
19
|
+
<div class="flex flex-col w-0 flex-1 bg-slate-100 dark:bg-slate-800 dark:border-slate-500 md:rounded-lg <%= BulletTrain::Themes::Light.navigation == :left ? "lg:border-l lg:rounded-l-none" : "" %>">
|
20
|
+
<main class="flex-1 relative z-0 focus:outline-none" tabindex="0">
|
21
|
+
<div class="py-2 px-1">
|
22
|
+
<div class="mx-auto px-4 sm:px-6 py-4">
|
23
|
+
<%= yield %>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
</main>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</body>
|
31
|
+
</html>
|