bullet_train 1.1.10 ā†’ 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/account/invitations/controller_base.rb +3 -11
  3. data/app/controllers/concerns/account/users/controller_base.rb +4 -25
  4. data/app/controllers/concerns/registrations/controller_base.rb +9 -6
  5. data/app/controllers/sessions_controller.rb +9 -0
  6. data/app/helpers/account/teams_helper.rb +1 -1
  7. data/app/helpers/concerns/helpers/base.rb +0 -9
  8. data/app/models/concerns/memberships/base.rb +3 -7
  9. data/app/models/concerns/users/base.rb +3 -0
  10. data/app/views/account/memberships/_index.html.erb +4 -35
  11. data/app/views/account/memberships/_membership.html.erb +29 -0
  12. data/app/views/account/memberships/index.html.erb +6 -1
  13. data/app/views/account/memberships/show.html.erb +1 -1
  14. data/app/views/account/onboarding/user_details/edit.html.erb +6 -6
  15. data/app/views/account/teams/_index.html.erb +1 -25
  16. data/app/views/account/teams/_team.html.erb +23 -0
  17. data/app/views/account/teams/index.html.erb +3 -1
  18. data/app/views/account/teams/show.html.erb +1 -1
  19. data/app/views/devise/registrations/new.html.erb +2 -2
  20. data/app/views/devise/sessions/new.html.erb +1 -1
  21. data/app/views/layouts/docs.html.erb +12 -0
  22. data/config/locales/en/base.yml +1 -0
  23. data/docs/application-options.md +29 -0
  24. data/docs/authentication.md +9 -0
  25. data/docs/field-partials/file-field.md +25 -0
  26. data/docs/field-partials.md +3 -1
  27. data/docs/i18n.md +28 -0
  28. data/docs/index.md +2 -0
  29. data/docs/invitation_only.md +1 -1
  30. data/docs/themes.md +13 -40
  31. data/docs/trademark.md +25 -0
  32. data/docs/two-factor-authentication.md +16 -0
  33. data/lib/bullet_train/resolver.rb +47 -0
  34. data/lib/bullet_train/version.rb +1 -1
  35. data/lib/bullet_train.rb +6 -1
  36. data/lib/tasks/bullet_train_tasks.rake +59 -1
  37. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 861c6f60ec10ff83389d2ac53d5a702dd230db869b890b6e46f84896fecb8b7b
4
- data.tar.gz: 55d51e53583ef3e2bdd179f821e7a48920483acea55adb818a736ca411106a70
3
+ metadata.gz: 4bb9dc7bc4b2ac1de4645e63ad3cf5da7bf2f4020365bf85efca8de4a4440a45
4
+ data.tar.gz: a328f4be111b4439a89ed0895bd92d16848fe81d9237e43c1f5def3759bed27a
5
5
  SHA512:
6
- metadata.gz: a5ddb5c77373a119154c1f1fd1993b9f89723dd0239f4e71402e3c7399100f38734edbd0bb9768d2545e607d98281da486ab7ac6c2d55e3b06da902eceebc4a3
7
- data.tar.gz: 47337cee5019ad52c09e325cb9ef035b24d59da94083337a817cc45342ea0c0feac586e115ac793b3bd802e20e1b985aecd479b603dbed2b1765dbaf2638333a
6
+ metadata.gz: 4f5892c3867e7ddb2530abe9516e004939c3dc8d35d1347d40e224a6e3d10d912fbb60d8cdc5ed62dcba895a459935ca1e7f3dd33b1b53f6dc31754de867895a
7
+ data.tar.gz: d2c9875372791ca7cf424da1141db44f1f31c2cb3b60cb6001ab80bf30a87bc63b7e52dd8c2808a9c8fcc412c9ee9419b8e137eb60f75439c3b5b30d0dd53811
@@ -49,17 +49,9 @@ module Account::Invitations::ControllerBase
49
49
 
50
50
  # unless the user is signed in.
51
51
  if !current_user.present?
52
- # keep track of the uuid of the invitation so we can reload it
53
- # after they sign up. at this point we don't even know if it's
54
- # valid, but that's fine.
55
- session[:invitation_uuid] = params[:id]
56
-
57
- # also, we'll queue devise up to return to the invitation url after a sign in.
58
- session["user_return_to"] = request.path
59
-
60
- # assume the user needs to create an account.
61
- # this is not the default for devise, but a sensible default here.
62
- redirect_to new_user_registration_path
52
+ # We need them to register.
53
+ # We have to send `invitation_uuid` via params, not session, because Safari doesn't set cookies on redirect.
54
+ redirect_to new_user_registration_path(invitation_uuid: @invitation&.uuid)
63
55
 
64
56
  # session[:invitation_uuid] should only be present if the user is registering for the first time.
65
57
  elsif (@invitation = Invitation.find_by(uuid: session[:invitation_uuid] || params[:id]))
@@ -10,6 +10,10 @@ module Account::Users::ControllerBase
10
10
  # for magic locales.
11
11
  @child_object = @user
12
12
  end
13
+
14
+ private
15
+
16
+ include strong_parameters_from_api
13
17
  end
14
18
 
15
19
  # GET /account/users/1/edit
@@ -53,29 +57,4 @@ module Account::Users::ControllerBase
53
57
  def process_params(strong_params)
54
58
  raise "It looks like you've removed `process_params` from your controller. This will break Super Scaffolding."
55
59
  end
56
-
57
- # Never trust parameters from the scary internet, only allow the white list through.
58
- # TODO Update this to use `include strong_parameters_from_api`.
59
- def user_params
60
- # TODO enforce permissions on updating the user's team name.
61
- strong_params = params.require(:user).permit(
62
- *([
63
- :email,
64
- :first_name,
65
- :last_name,
66
- :time_zone,
67
- :current_password,
68
- :password,
69
- :password_confirmation,
70
- :profile_photo_id,
71
- :locale,
72
- ] + permitted_fields + [
73
- {
74
- current_team_attributes: [:name]
75
- }.merge(permitted_arrays)
76
- ])
77
- )
78
-
79
- process_params(strong_params)
80
- end
81
60
  end
@@ -3,6 +3,12 @@ module Registrations::ControllerBase
3
3
 
4
4
  included do
5
5
  def new
6
+ # We have to set the session here because Safari wouldn't save it on a redirect to this URL.
7
+ if params[:invitation_uuid]
8
+ session[:invitation_uuid] = params[:invitation_uuid]
9
+ session["user_return_to"] = accept_account_invitation_path(params[:invitation_uuid])
10
+ end
11
+
6
12
  if invitation_only?
7
13
  unless session[:invitation_uuid] || session[:invitation_key]
8
14
  return redirect_to root_path
@@ -19,17 +25,14 @@ module Registrations::ControllerBase
19
25
 
20
26
  # if current_user is defined, that means they were successful registering.
21
27
  if current_user
22
- # if the user doesn't have a team at this point, create one.
23
- # If the user is accepting an invitation, then the user's current_team is populated
24
- # with the information attached to their invitation via `@invitation.accept_for` later on,
25
- # so we don't have to create a default team for them here.
26
- unless current_user.teams.any? || session[:invitation_uuid].present?
28
+ # Don't create a default team if they're being invited to another team.
29
+ # Don't create a default team if they have another one for any reason.
30
+ unless session[:invitation_uuid].present? || current_user.teams.any?
27
31
  current_user.create_default_team
28
32
  end
29
33
 
30
34
  # send the welcome email.
31
35
  current_user.send_welcome_email unless current_user.email_is_oauth_placeholder?
32
-
33
36
  end
34
37
  end
35
38
  end
@@ -1,6 +1,15 @@
1
1
  class SessionsController < Devise::SessionsController
2
2
  include Sessions::ControllerBase
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
+ helper_method :user_return_to_is_oauth
12
+
4
13
  def destroy
5
14
  if params.include?(:onboard_logout)
6
15
  signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
@@ -2,7 +2,7 @@ module Account::TeamsHelper
2
2
  def current_team
3
3
  # TODO We do not want this to be based on the `current_team_id`.
4
4
  # TODO We want this to be based on the current resource being loaded.
5
- current_user&.current_team
5
+ @team || current_user&.current_team
6
6
  end
7
7
 
8
8
  def other_teams
@@ -6,13 +6,4 @@ module Helpers::Base
6
6
  # This scope has an order if the SQL changes when we remove any order clause.
7
7
  scope.to_sql != scope.reorder("").to_sql
8
8
  end
9
-
10
- # TODO This should really be in the API package and included from there.
11
- if defined?(BulletTrain::Api)
12
- def render_pagination(json)
13
- if @pagy
14
- json.has_more @pagy.has_more
15
- end
16
- end
17
- end
18
9
  end
@@ -27,7 +27,7 @@ module Memberships::Base
27
27
 
28
28
  scope :current_and_invited, -> { includes(:invitation).where("user_id IS NOT NULL OR invitations.id IS NOT NULL").references(:invitation) }
29
29
  scope :current, -> { where("user_id IS NOT NULL") }
30
- scope :tombstones, -> { includes(:invitation).where("user_id IS NULL AND invitations.id IS NULL").references(:invitation) }
30
+ scope :tombstones, -> { includes(:invitation).where("user_id IS NULL AND invitations.id IS NULL AND platform_agent IS FALSE").references(:invitation) }
31
31
 
32
32
  # TODO Probably we can provide a way for gem packages to define these kinds of extensions.
33
33
  if billing_enabled?
@@ -62,7 +62,7 @@ module Memberships::Base
62
62
  end
63
63
 
64
64
  def tombstone?
65
- user.nil? && invitation.nil? && !platform_agent?
65
+ user.nil? && invitation.nil? && !platform_agent
66
66
  end
67
67
 
68
68
  def last_admin?
@@ -130,7 +130,7 @@ module Memberships::Base
130
130
  end
131
131
 
132
132
  def first_name_last_initial
133
- [first_name, last_initial].map(&:present?).join(" ")
133
+ [first_name, last_initial].select(&:present?).join(" ")
134
134
  end
135
135
 
136
136
  # TODO utilize this.
@@ -138,8 +138,4 @@ module Memberships::Base
138
138
  def should_receive_notifications?
139
139
  invitation.present? || user.present?
140
140
  end
141
-
142
- def platform_agent?
143
- platform_agent_of_id.present?
144
- end
145
141
  end
@@ -109,10 +109,12 @@ module Users::Base
109
109
  end
110
110
  end
111
111
 
112
+ # TODO https://github.com/bullet-train-co/bullet_train-base/pull/121 should have removed this, but it caused errors.
112
113
  def administrating_team_ids
113
114
  parent_ids_for(Role.admin, :memberships, :team)
114
115
  end
115
116
 
117
+ # TODO https://github.com/bullet-train-co/bullet_train-base/pull/121 should have removed this, but it caused errors.
116
118
  def parent_ids_for(role, through, parent)
117
119
  parent_id_column = "#{parent}_id"
118
120
  key = "#{role.key}_#{through}_#{parent_id_column}s"
@@ -125,6 +127,7 @@ module Users::Base
125
127
  value
126
128
  end
127
129
 
130
+ # TODO https://github.com/bullet-train-co/bullet_train-base/pull/121 should have removed this, but it caused errors.
128
131
  def invalidate_ability_cache
129
132
  update_column(:ability_cache, {})
130
133
  end
@@ -6,12 +6,12 @@
6
6
  <%= render 'account/shared/box' do |p| %>
7
7
  <% p.content_for :title, t(".contexts.#{context.class.name.underscore}.header") %>
8
8
  <% p.content_for :description do %>
9
- <%= raw t(".contexts.#{context.class.name.underscore}.#{memberships.any? ? 'description' : 'description_empty'}") %>
10
- <%= render "shared/limits/index", model: memberships.model %>
9
+ <%= raw t(".contexts.#{context.class.name.underscore}.#{@memberships.any? ? 'description' : 'description_empty'}") %>
10
+ <%= render "shared/limits/index", model: @memberships.model %>
11
11
  <% end %>
12
12
 
13
13
  <% p.content_for :table do %>
14
- <% if memberships.any? %>
14
+ <% if @memberships.any? %>
15
15
  <table class="table">
16
16
  <thead>
17
17
  <tr>
@@ -22,38 +22,7 @@
22
22
  </tr>
23
23
  </thead>
24
24
  <tbody data-model="Membership" data-scope="current">
25
- <% memberships.each do |membership| %>
26
- <tr data-id="<%= membership.id %>">
27
-
28
- <td class="px-6 py-4 whitespace-nowrap">
29
- <%= link_to [:account, membership], class: 'block flex items-center group hover:no-underline no-underline' do %>
30
- <div class="flex-shrink-0 h-10 w-10">
31
- <%= image_tag membership_profile_photo_url(membership), title: membership.label_string, class: 'h-10 w-10 rounded-full' %>
32
- </div>
33
-
34
- <div class="ml-3">
35
- <span class="group-hover:underline"><%= membership.label_string %></span>
36
- <% if membership.unclaimed? %>
37
- <span class="ml-1.5 px-2 inline-flex text-xs text-green-dark bg-green-light border border-green-dark rounded-md">
38
- Invited
39
- </span>
40
- <% end %>
41
- </div>
42
- <% end %>
43
- </td>
44
-
45
- <td>
46
- <% if membership.roles_without_defaults.any? %>
47
- <%= membership.roles_without_defaults.map { |role| t("memberships.fields.role_ids.options.#{role.key}.label") }.to_sentence %>
48
- <% else %>
49
- <%= t("memberships.fields.role_ids.options.default.label") %>
50
- <% end %>
51
- </td>
52
- <td class="text-right">
53
- <%= link_to t('.buttons.show'), [:account, membership], class: 'button-secondary button-smaller' %>
54
- </td>
55
- </tr>
56
- <% end %>
25
+ <%= yield %>
57
26
  </tbody>
58
27
  </table>
59
28
  <% end %>
@@ -0,0 +1,29 @@
1
+ <tr data-id="<%= membership.id %>">
2
+ <td class="px-6 py-4 whitespace-nowrap">
3
+ <%= link_to [:account, membership], class: 'block flex items-center group hover:no-underline no-underline' do %>
4
+ <div class="flex-shrink-0 h-10 w-10">
5
+ <%= image_tag membership_profile_photo_url(membership), title: membership.label_string, class: 'h-10 w-10 rounded-full' %>
6
+ </div>
7
+
8
+ <div class="ml-3">
9
+ <span class="group-hover:underline"><%= membership.label_string %></span>
10
+ <% if membership.unclaimed? %>
11
+ <span class="ml-1.5 px-2 inline-flex text-xs text-green-dark bg-green-light border border-green-dark rounded-md">
12
+ Invited
13
+ </span>
14
+ <% end %>
15
+ </div>
16
+ <% end %>
17
+ </td>
18
+
19
+ <td>
20
+ <% if membership.roles_without_defaults.any? %>
21
+ <%= membership.roles_without_defaults.map { |role| t("memberships.fields.role_ids.options.#{role.key}.label") }.to_sentence %>
22
+ <% else %>
23
+ <%= t("memberships.fields.role_ids.options.default.label") %>
24
+ <% end %>
25
+ </td>
26
+ <td class="text-right">
27
+ <%= link_to t('.buttons.show'), [:account, membership], class: 'button-secondary button-smaller' %>
28
+ </td>
29
+ </tr>
@@ -1,7 +1,12 @@
1
1
  <%= render 'account/shared/page' do |p| %>
2
2
  <% p.content_for :title, t('.section') %>
3
3
  <% p.content_for :body do %>
4
- <%= render 'index', memberships: @memberships.current_and_invited.includes(:user) if @memberships.current_and_invited.any? %>
4
+ <% if @memberships.current_and_invited.any? %>
5
+ <%= render 'index' do %>
6
+ <%= render @memberships.current_and_invited.includes(:user) %>
7
+ <% end %>
8
+ <% end %>
9
+
5
10
  <%= render 'tombstones', memberships: @memberships.tombstones.includes(:user) if @memberships.tombstones.any? %>
6
11
  <% end %>
7
12
  <% end %>
@@ -46,7 +46,7 @@
46
46
  <% else %>
47
47
  <%= link_to t('.buttons.promote'), [:promote, :account, @membership], method: :post, data: { confirm: t('global.confirm_message') }, class: first_button_primary if can? :promote, @membership %>
48
48
  <% end %>
49
- <% if (can? :destroy, @membership) && (!@membership.platform_agent?) %>
49
+ <% if (can? :destroy, @membership) && (!@membership.platform_agent) %>
50
50
  <%= button_to t(".buttons.#{membership_destroy_locale_key(@membership)}"), [:account, @membership], method: :delete, data: { confirm: t(".buttons.confirmations.#{membership_destroy_locale_key(@membership)}", model_locales(@membership)) }, class: first_button_primary %>
51
51
  <% end %>
52
52
  <% end %>
@@ -16,15 +16,15 @@
16
16
  <%= render 'shared/fields/text_field', form: f, method: :last_name %>
17
17
  </div>
18
18
 
19
- <div class="sm:col-span-2">
20
- <% # only edit the team name if this user is an admin and they are the first user. %>
21
- <% # yes, that's redundant. %>
22
- <% if can?(:edit, f.object.current_team) && f.object.current_team.users.count == 1 %>
19
+ <% # only edit the team name if this user is an admin and they are the first user. %>
20
+ <% # yes, that's redundant. %>
21
+ <% if can?(:edit, f.object.current_team) && f.object.current_team.users.count == 1 %>
22
+ <div class="sm:col-span-2">
23
23
  <%= f.fields_for :current_team do |tf| %>
24
24
  <%= render 'shared/fields/text_field', form: tf, method: :name %>
25
25
  <% end %>
26
- <% end %>
27
- </div>
26
+ </div>
27
+ <% end %>
28
28
 
29
29
  <div class="sm:col-span-2">
30
30
  <%= render 'shared/fields/super_select', form: f, method: :time_zone,
@@ -1,29 +1,5 @@
1
1
  <ul class="space-y">
2
- <% @teams.each do |team| %>
3
- <li class="bg-white shadow overflow-hidden sm:rounded-md dark:bg-sealBlue-400">
4
- <%= link_to [:account, team], class: "group block hover:bg-gray-50 dark:hover:bg-sealBlue-400 dark:text-sealBlue-800" do %>
5
- <div class="px-4 py-4 flex items-center sm:pl-8 sm:pr-6">
6
- <div class="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between">
7
- <div>
8
- <div class="flex text-xl font-semibold text-blue uppercase group-hover:text-blue-dark tracking-widest dark:text-white">
9
- <%= team.name %>
10
- </div>
11
- </div>
12
- <div class="mt-4 flex-shrink-0 sm:mt-0">
13
- <div class="flex overflow-hidden">
14
- <%= render 'account/shared/memberships/photos', memberships: team.memberships.current_and_invited.first(10) %>
15
- </div>
16
- </div>
17
- </div>
18
- <div class="ml-5 flex-shrink-0">
19
- <svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
20
- <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
21
- </svg>
22
- </div>
23
- </div>
24
- <% end %>
25
- </li>
26
- <% end %>
2
+ <%= yield %>
27
3
  </ul>
28
4
 
29
5
  <% if show_sign_up_options? && can?(:create, Team.new) %>
@@ -0,0 +1,23 @@
1
+ <li class="bg-white shadow overflow-hidden sm:rounded-md dark:bg-sealBlue-400">
2
+ <%= link_to [:account, team], class: "group block hover:bg-gray-50 dark:hover:bg-sealBlue-400 dark:text-sealBlue-800" do %>
3
+ <div class="px-4 py-4 flex items-center sm:pl-8 sm:pr-6">
4
+ <div class="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between">
5
+ <div>
6
+ <div class="flex text-xl font-semibold text-blue uppercase group-hover:text-blue-dark tracking-widest dark:text-white">
7
+ <%= team.name %>
8
+ </div>
9
+ </div>
10
+ <div class="mt-4 flex-shrink-0 sm:mt-0">
11
+ <div class="flex overflow-hidden">
12
+ <%= render 'account/shared/memberships/photos', memberships: team.memberships.current_and_invited.first(10) %>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ <div class="ml-5 flex-shrink-0">
17
+ <svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
18
+ <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
19
+ </svg>
20
+ </div>
21
+ </div>
22
+ <% end %>
23
+ </li>
@@ -1,6 +1,8 @@
1
1
  <%= render 'account/shared/page' do |p| %>
2
2
  <% p.content_for :title, t('.section') %>
3
3
  <% p.content_for :body do %>
4
- <%= render 'index', creative_concepts: @creative_concepts %>
4
+ <%= render 'index', creative_concepts: @creative_concepts do %>
5
+ <%= render @teams %>
6
+ <% end %>
5
7
  <% end %>
6
8
  <% end %>
@@ -13,7 +13,7 @@
13
13
  <% unless scaffolding_things_disabled? %>
14
14
  <%= render 'account/shared/commentary/box' do |p| %>
15
15
  <% p.content_for :content do %>
16
- <%= render 'account/scaffolding/absolutely_abstract/creative_concepts/index', creative_concepts: @team.scaffolding_absolutely_abstract_creative_concepts.accessible_by(current_ability), hide_back: true unless scaffolding_things_disabled? %>
16
+ <%= render 'account/scaffolding/absolutely_abstract/creative_concepts/index', creative_concepts: @team.scaffolding_absolutely_abstract_creative_concepts, hide_back: true unless scaffolding_things_disabled? %>
17
17
  <% end %>
18
18
 
19
19
  <% p.content_for :commentary do %>
@@ -22,9 +22,9 @@
22
22
  </div>
23
23
 
24
24
  <%= f.submit t('global.buttons.sign_up'), class: 'button full' %>
25
-
26
- <%= render 'devise/shared/oauth', verb: 'Sign Up' %>
27
25
  <% end %>
26
+
27
+ <%= render 'devise/shared/oauth', verb: 'Sign Up' %>
28
28
  <% end %>
29
29
  <% end %>
30
30
  <% end %>
@@ -2,7 +2,7 @@
2
2
  <% p.content_for :title, t('devise.headers.sign_in') %>
3
3
  <% p.content_for :body do %>
4
4
  <% within_fields_namespace(:self) do %>
5
- <%= 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| %>
5
+ <%= 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, data: {turbo: !user_return_to_is_oauth} do |form| %>
6
6
  <% with_field_settings form: form do %>
7
7
  <%= render 'account/shared/notices', form: form %>
8
8
  <%= render 'account/shared/forms/errors', form: form %>
@@ -108,6 +108,12 @@
108
108
  <i class="fa-brands fa-js ti ti-pulse"></i>
109
109
  <% end %>
110
110
  <% end %>
111
+
112
+ <%= render 'account/shared/menu/item', url: '/docs/i18n', label: 'Internationalzation' do |p| %>
113
+ <% p.content_for :icon do %>
114
+ <i class="fa-brands fa-js ti ti-world"></i>
115
+ <% end %>
116
+ <% end %>
111
117
  <% end %>
112
118
 
113
119
  <%= render 'account/shared/menu/section', title: 'Developer Tools' do %>
@@ -140,6 +146,12 @@
140
146
  <i class="fal fa-check ti ti-video-camera"></i>
141
147
  <% end %>
142
148
  <% end %>
149
+
150
+ <%= render 'account/shared/menu/item', url: 'docs/application-options.md', label: 'Application Options' do |p| %>
151
+ <% p.content_for :icon do %>
152
+ <i class="fal fa-gear ti ti-settings"></i>
153
+ <% end %>
154
+ <% end %>
143
155
  <% end %>
144
156
 
145
157
  <%= render 'account/shared/menu/section', title: 'Accounts & Teams' do %>
@@ -77,6 +77,7 @@ en:
77
77
  one: Last Day
78
78
  other: Last %{count} Days
79
79
  formats:
80
+ timestamp_unavailable: Never
80
81
  date: '%m/%d/%Y'
81
82
  date_and_time: '%m/%d/%Y %l:%M %p'
82
83
 
@@ -0,0 +1,29 @@
1
+ # Application Options
2
+
3
+ Bullet Train features a list of options available at your disposal to enable/disable functionalities that would otherwise take a significant amount of time to implement. Simply add any of the following environment variables to `config/application.yml` in your main Bullet Train application and restart your server for the options to apply.
4
+
5
+ The helper methods below can also be directly invoked in your application if you wish to have parts of your code depend on the functionality in question.
6
+
7
+ | Option | Type | Example | Helper Methods |
8
+ | --- | --- | --- | --- |
9
+ | HIDE_THINGS | Boolean | `"true"` | `scaffolding_things_disabled?` |
10
+ | HIDE_EXAMPLES | Boolean | `"true"` | `scaffolding_things_disabled?` |
11
+ | STRIPE_CLIENT_ID | String | `"your_stripe_client_id"` | `stripe_enabled?` |
12
+ | CLOUDINARY_URL | String | `"cloudinary://your_cloudinary_token_here"` | `cloudinary_enabled?` |
13
+ | TWO_FACTOR_ENCRYPTION_KEY | String | `"your_encryption_key"` | `two_factor_enabled_authentication?` |
14
+ | INVITATION_KEYS | String | `"ofr9h5h9ghzeodh, ofr9h5h9ghzeodi"` | `invitation_keys` `invitation_only?` |
15
+ | FONTAWESOME_NPM_AUTH_TOKEN | String | `"your_font_awesome_token"` | `font_awesome?` |
16
+ | SILENCE_LOGS | Boolean | `"true"` | `silence_logs?` |
17
+ | TESTING_PROVISION_KEY | String | `"asdf123"` | N/A |
18
+
19
+ | Option | Description |
20
+ | --- | --- |
21
+ | HIDE_THINGS | Hides Bullet Train demo models such as `CreativeConcept` and `TangibleThing`. |
22
+ | HIDE_EXAMPLES | Hides base models such as `CreativeConcept` and `TangibleThing`.
23
+ | STRIPE_CLIENT_ID | See [Bullet Train Billing for Stripe](/docs/billing/stripe.md) for more information and related environment variables. |
24
+ | CLOUDINARY_URL | Enables use of Cloudinary for handling images. |
25
+ | TWO_FACTOR_ENCRYPTION_KEY | Enables two-factor authentication through Devise. |
26
+ | INVITATION_KEYS | See more [Invitation Only](/docs/invitation_only.md) for more information. |
27
+ | FONTAWESOME_NPM_AUTH_TOKEN | Enables use of Font Awesome. |
28
+ | SILENCE_LOGS | Silences Super Scaffolding logs. |
29
+ | TESTING_PROVISION_KEY | Creates a test `Platform::Application` by accessing `/testing/provision?key=your_provision_key` |
@@ -11,3 +11,12 @@ bin/resolve SessionsController --eject --open
11
11
 
12
12
  ## Customizing Views
13
13
  You can customize Devise views using the same workflow you would use to customize any other Bullet Train views.
14
+
15
+ ## Disabling Registration
16
+
17
+ Registration is enabled by default. You can disable registration, allowing signups via an invite code only, by using [Invitation Only Mode](/docs/invitation_only.md)
18
+
19
+ ## Two factor authentication
20
+
21
+ This feature allows users to add two factor authentication.
22
+ It requires some setup - [Two Factor Authentication](/docs/two-factor-authentication.md)
@@ -0,0 +1,25 @@
1
+ # Examples and setup for the `file_field` Field Partial
2
+
3
+ ## Active Storage
4
+
5
+ `file_field` is designed to be used with [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html). You will need to confgure Active Storage for your application before using this field partial. You can find instructions for doing so in the [Rails Guides](https://edgeguides.rubyonrails.org/active_storage_overview.html#setup).
6
+
7
+ In addition, Bullet Train has integrated the direct-uploads feature of Active Storage. For this to work, you need to have CORS configured for your storage endpoint. You can find instructions for doing so in the [Rails Guides](https://edgeguides.rubyonrails.org/active_storage_overview.html#cross-origin-resource-sharing-cors-configuration).
8
+
9
+ ## Example
10
+
11
+ Add a 'document' file as an attachment to a `Post` model:
12
+
13
+ Add the following to `app/models/post.rb`:
14
+
15
+ ```ruby
16
+ has_one_attached :document
17
+ ```
18
+
19
+ Note, no database migration is required as ActiveStorage uses its own tables to store the attachments.
20
+
21
+ Run the following command to generate the scaffolding for the `document` field on the `Post` model:
22
+
23
+ ```bash
24
+ ./bin/super-scaffold crud-field Post document:file_field
25
+ ```
@@ -118,7 +118,7 @@ Certain form field partials like `buttons` and `super_select` can also have thei
118
118
  | `date_and_time_field` | `datetime` | | `assign_date_and_time` | [Date Range Picker](https://www.daterangepicker.com) | | |
119
119
  | `date_field` | `date` | | `assign_date` | [Date Range Picker](https://www.daterangepicker.com) | | |
120
120
  | `email_field` | `string` | | | | | |
121
- | `file_field` | `attachment` | | [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) | | |
121
+ | [`file_field`](/docs/field-partials/file-field.md) | `attachment` | | [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) | | |
122
122
  | `options` | `string` | Optionally | `assign_checkboxes` | | | |
123
123
  | `password_field` | `string` | | | | | |
124
124
  | `phone_field` | `string` | | | [International Telephone Input](https://intl-tel-input.com) | Ensures telephone numbers are in a format that can be used by providers like Twilio. | |
@@ -137,3 +137,5 @@ Set the data type to `jsonb` whenever passing the `multiple` option to a new att
137
137
  ## Additional Field Partials Documentation
138
138
  - [`buttons`](/docs/field-partials/buttons.md)
139
139
  - [`super_select`](/docs/field-partials/super-select.md)
140
+ - [`file_field`](/docs/field-partials/file-field.md)
141
+
data/docs/i18n.md CHANGED
@@ -1,3 +1,31 @@
1
1
  # Translations and Internationalization
2
2
 
3
3
  Bullet Train and views generated by Super Scaffolding are localized by default, meaning all of the human-readable text in your application has been extracted into a YAML configuration file in `config/locales/en` and can be translated into any language you would like to target.
4
+
5
+ We override the native I18n translation method to automatically include the current team name or other objects depending on the string. For example, here's a description of a membership which you can find on a membership's show page:
6
+
7
+ ```
8
+ The following are the details for Davidā€™s Membership on Your Team.
9
+ ```
10
+
11
+ The view can be found here in the `bullet_train-base` gem:<br/>[bullet_train-base/app/views/account/memberships/show.html.erb](https://github.com/bullet-train-co/bullet_train-base/blob/657e932cb4eb3e0c1f56c88c8365c2611de90e06/app/views/account/memberships/show.html.erb#L16)<br/>
12
+ <br/>
13
+ Looking at the view, you can see we are only passing a key to the translation method for I18n to process:
14
+
15
+ ```erb
16
+ <%= t('.description') %>
17
+ ```
18
+
19
+ However, looking at the [locale itself](https://github.com/bullet-train-co/bullet_train-base/blob/657e932cb4eb3e0c1f56c88c8365c2611de90e06/config/locales/en/memberships.en.yml#L82), you can see that the string takes two variables, `memberships_possessive` and `team_name`, to complete the string:
20
+ ```yaml
21
+ description: The following are the details for %{memberships_possessive} Membership on %{team_name}.
22
+ ```
23
+
24
+ Usually, you would pass the variable as a keyword argument:
25
+ ```ruby
26
+ t('.description', memberships_possessive: memberships_possessive, team_name: current_team.name)
27
+ ```
28
+
29
+ However, in Bullet Train, we override the original translation method to include variable names like this automatically in our locales. Check out the [locale helper](https://github.com/bullet-train-co/bullet_train-base/blob/main/app/helpers/account/locale_helper.rb) to get a closer look at how we handle strings for internationalization. For example, the two variables above are generated by the method `model_locales` in the locale helper.
30
+
31
+ You can find more information in the [indirection documentation](indirection) about using `bin/resolve` and logs to pinpoint where your locales are coming from.
data/docs/index.md CHANGED
@@ -13,6 +13,7 @@
13
13
  - [Overriding the Framework](/docs/overriding.md)
14
14
  - [Setting up a Tunnel](/docs/tunneling.md)
15
15
  - [JavaScript](/docs/javascript.md)
16
+ - [Internationalization](/docs/i18n.md)
16
17
 
17
18
  ## Developer Tools
18
19
  - [Super Scaffolding](/docs/super-scaffolding.md)
@@ -20,6 +21,7 @@
20
21
  - [Database Seeds](/docs/seeds.md)
21
22
  - [Test Suite](/docs/testing.md)
22
23
  - [Point-and-Click Test Writing](https://github.com/bullet-train-co/magic_test) <i class="ti ti-new-window ml-2"></i>
24
+ - [Application Options](/docs/application-options.md)
23
25
 
24
26
  ## Accounts & Teams
25
27
  - [Authentication](/docs/authentication.md)
@@ -9,5 +9,5 @@ INVITATION_KEYS: ofr9h5h9ghzeodh
9
9
 
10
10
  In this case, the user will be able to register their own account by accessing the following link:
11
11
  ```
12
- http://localhost:3000/invitations?key=ofr9h5h9ghzeodh
12
+ http://localhost:3000/invitation?key=ofr9h5h9ghzeodh
13
13
  ```
data/docs/themes.md CHANGED
@@ -1,27 +1,21 @@
1
1
  # Themes
2
2
 
3
- Bullet Train has a theme subsystem designed to allow you the flexibility to either extend or completely replace the stock ā€œLightā€ and ā€œCleanā€ UI templates.
3
+ Bullet Train has a theme subsystem designed to allow you the flexibility to either extend or completely replace the stock ā€œLightā€ UI template.
4
+ To reduce duplication of code across themes, Bullet Train implements the following three packages:
5
+ 1. `bullet_train-themes`
6
+ 2. `bullet_train-themes-tailwind_css`
7
+ 3. `bullet_train-themes-light`
4
8
 
5
- ## Inheritance Structure
9
+ This is where all of Bullet Train's standard views are contained.
6
10
 
7
- To reduce duplication of code across themes, Bullet Train implements an inheritance structure. For example, the official Bullet Train themes are structured hierarchically like so:
11
+ ## Adding a New Theme (ejecting standard views)
8
12
 
9
- - ā€œBaseā€
10
- - ā€œBootstrapā€ (in the future)
11
- - ā€œCleanā€ (in the future)
12
- - ā€œTailwind CSSā€
13
- - ā€œLightā€
13
+ If you want to add a new theme, you can use the following command. This will copy all of the standard views from `bullet_train-themes-light` to `app/views/themes/` and configure your application to use the new theme. For example, let's make a new theme called "foo":
14
+ `> rake bullet_train:themes:light:eject[foo]`
14
15
 
15
- Any component partials that can be shared are pushed up the inheritance structure. For example, [Bullet Train's library of field partials](/docs/field-partials.md) provide a good example of this, illustrating the power of the approach weā€™ve taken here:
16
+ After running this command, you will see that a few other files are edited to use this new theme. Whenever switching a theme, you will need to make the same changes to make sure your application is running with the theme of your choice.
16
17
 
17
- - The most general field styling varies substantially between Tailwind CSS and Bootstrap, so a `_field.html.erb` component partial exists in both the foundational ā€œTailwind CSSā€ and ā€œBootstrapā€ themes, but also a further customized version exists in themes like ā€œLightā€.
18
- - However, many concrete field types like `_text_field.html.erb` and `_phone_field.html.erb` leverage `_field.html.erb`, and they themselves are completely framework agnostic as a result. These partials can live in the shared ā€œBaseā€ theme.
19
-
20
- At run-time, this means:
21
-
22
- - When rendering `_text_field.html.erb`, it renders from ā€œBaseā€.
23
- - However, when `_text_field.html.erb` references `_field.html.erb`, that renders from ā€œLightā€.
24
- - If you extend ā€œLightā€ and override `_field.html.erb`, rendering `_text_field.html.erb` will now use your themeā€™s `_field.html.erb`.
18
+ You can also pass an annotated path to a view after running `bin/resolve` to eject individual views to your application.
25
19
 
26
20
  ## Theme Component Usage
27
21
 
@@ -35,32 +29,11 @@ We say "within" because while a `shared` view partial directory does exist, the
35
29
 
36
30
  ### Dealing with Indirection
37
31
 
38
- This small piece of indirection buys us an incredible amount of power in building and extending themes, but as with any indirection, it could potentially come at the cost of developer experience. That's why Bullet Train includes additional tools for smoothing over this experience. Be sure to read the section on [dealing with indirection].
39
-
32
+ This small piece of indirection buys us an incredible amount of power in building and extending themes, but as with any indirection, it could potentially come at the cost of developer experience. That's why Bullet Train includes additional tools for smoothing over this experience. Be sure to read the section on [dealing with indirection](./indirection.md).
40
33
 
41
34
  ## Theme Configuration
42
35
 
43
- You can specify the theme youā€™d like to use and its inheritance structure in `app/helpers/theme_helper.rb`. The code there is well commented to help you.
44
-
45
- ## Theme Structure
46
-
47
- Themes are represented in a few places. Taking ā€œLightā€ as an example, we have:
48
-
49
- - A directory of theme-specific component partials in `app/views/themes/light`, including a layout ERB template.
50
- - A theme-specific stylesheet in `app/javascript/stylesheets/light/application.scss`.
51
- - A theme-specific pack in `app/javascript/packs/light.js`. Youā€™ll see there that the actual JavaScript dependencies and code are shared across all themes. The whole purpose of this theme-specific pack is to serve up the theme-specific stylesheet.
52
- - Theme-specific logos and images in `app/javascript/images/light`.
53
-
54
- ## Adding a New Theme
55
-
56
- To extend the ā€œLightā€ theme in a new theme called ā€œTokyoā€, we would:
57
-
58
- 1. Copy `app/javascript/packs/light.js` to `app/javascript/packs/tokyo.js` and update references to `light` therein to `tokyo`.
59
- 2. Copy `app/views/themes/light/layouts` to `app/views/themes/tokyo/layouts` and update references to `light` in the contained files to `tokyo`. It's possible this is too much duplication, but in practice most people want to customize these two layouts in their custom themes.
60
- 3. Create a new file at `app/javascript/stylesheets/tokyo/application.scss`. To start just add `@import "../light/application";` at the top, which represents the fact that ā€œTokyoā€ extends ā€œLightā€. Any custom styles can be added below that.
61
- 4. Add `"tokyo"` as the first item in the `THEME_DIRECTORY_ORDER` array in `app/helpers/theme_helper.rb`.
62
-
63
- You should be good to go! We'll try to add a generator for this in the future.
36
+ Your application will automatically be configured to use your new theme whenever you run the eject command. Run `> rake bullet_train:themes:light:install` to re-install the standard light theme.
64
37
 
65
38
  ## Additional Guidance and Principles
66
39
 
data/docs/trademark.md ADDED
@@ -0,0 +1,25 @@
1
+ # Trademark Information
2
+
3
+ "Bullet Train" is a registered trademark of Bullet Train, Inc.
4
+
5
+ We love and encourage contributions to the Bullet Train ecosystem. That's our dream!
6
+
7
+ It's also important when you're building packages or presenting offerings in the Bullet Train ecosystem, they're not named in such a way that anyone could confuse your package or offering as being officially maintained or sanctioned by the Bullet Train team.
8
+
9
+ Here are some examples of names we would not typically object to:
10
+
11
+ - "Super Widget __for Bullet Train__"
12
+ - "MySQL Starter Kit __for Bullet Train__"
13
+ - "__BT__ Starter Kit with MySQL"
14
+ - "__BT__ Pro Tools"
15
+
16
+ Here are some examples that would require explicit permission:
17
+
18
+ - "Bullet Train Super Widget"
19
+ - "Bullet Train MySQL Starter Kit"
20
+ - "Bullet Train Application Template with MySQL"
21
+ - "Bullet Train Pro Tools"
22
+ - "Bullet Train Conference"
23
+ - "Bullet Train Podcast"
24
+
25
+ If you've got an idea and aren't sure about the name, message Andrew Culver privately [on Discord](https://discord.gg/bullettrain) or [via Twitter DM](https://twitter.com/andrewculver). If you know anything about trademarks, you know we're required to enforce our policies, so thank you for understanding! šŸ™
@@ -0,0 +1,16 @@
1
+ # Two Factor Authentication
2
+
3
+ ## Setup
4
+
5
+ run `bin/rails db:encryption:init` and use `bin/rails credentials:edit` to add the resulting keys to your `secrets.yml`
6
+
7
+ Add the following gems to your `Gemfile` and run `bundle install`
8
+
9
+ ```ruby
10
+ gem "devise-two-factor"
11
+ gem "rqrcode"
12
+ ```
13
+
14
+ If you haven't already done so, set the environment variable `RAILS_MASTER_KEY` with the contents of `config/master.key`. Note, this file should not be committed to git, and you should keep it in a safe place.
15
+
16
+ Now in the user's Account Details page there will be an option to enable two factor, and when enabled the two factor code will be required at login.
@@ -87,6 +87,18 @@ module BulletTrain
87
87
  }
88
88
 
89
89
  result[:absolute_path] = file_path || class_path || partial_path || locale_path
90
+
91
+ # If we get the partial resolver template itself, that means we couldn't find the file.
92
+ if result[:absolute_path].match?("app/views/bullet_train/partial_resolver.html.erb")
93
+ puts "We could not find the partial you're looking for: #{@needle}".red
94
+ puts ""
95
+ puts "Please try passing the partial string using either of the following two ways:"
96
+ puts "1. Without underscore and extention: ".blue + "bin/resolve shared/attributes/code"
97
+ puts "2. Literal path with package name: ".blue + "bin/resolve bullet_train-themes/app/views/themes/base/attributes/_code.html.erb"
98
+ puts ""
99
+ exit
100
+ end
101
+
90
102
  if result[:absolute_path]
91
103
  if result[:absolute_path].include?("/bullet_train")
92
104
  base_path = "bullet_train" + result[:absolute_path].partition("/bullet_train").last
@@ -123,6 +135,41 @@ module BulletTrain
123
135
  end
124
136
 
125
137
  def partial_path
138
+ # Parse literal partial strings.
139
+ if @needle.match?(/\.html\.erb$/)
140
+ partial_parts = @needle.split("/")
141
+
142
+ # TODO: We should probably just default to raising an error if the developer
143
+ # provides a literal partial string without the name of the package it's coming from.
144
+ if partial_parts.size <= 3
145
+ # If the string looks something like "shared/attributes/_code.html.erb",
146
+ # all we need to do is change it to "shared/attributes/code"
147
+ partial_parts.last.gsub!(/(_)|(\.html\.erb)/, "")
148
+ @needle = partial_parts.join("/")
149
+ elsif @needle.match?(/bullet_train-/)
150
+ # If it's a full path, we need to make sure we're getting it from the right package.
151
+ _, partial_view_package, partial_path_without_package = @needle.partition(/bullet_train-[a-z|\-_0-9.]*/)
152
+
153
+ # Pop off the version so we can call `bundle show` correctly.
154
+ # Also change `bullet_train-base` to `bullet_train`.
155
+ partial_view_package.gsub!(/[-|.0-9]*$/, "") if partial_view_package.match?(/[-|.0-9]*$/)
156
+ partial_view_package.gsub!("-base", "") if /base/.match?(@needle)
157
+
158
+ local_package_path = `bundle show #{partial_view_package}`.chomp
159
+ return local_package_path + partial_path_without_package
160
+ else
161
+ puts "You passed the absolute path for a partial literal, but we couldn't find the package name in the string:".red
162
+ puts "`#{@needle}`".red
163
+ puts ""
164
+ puts "Check the string one more time to see if the package name is there."
165
+ puts "i.e.: bullet_train-base/app/views/layouts/devise.html.erb".blue
166
+ puts ""
167
+ puts "If you're not sure what the package name is, run `bin/resolve --interactive`, follow the prompt, and pass the annotated path."
168
+ puts "i.e.: <!-- BEGIN /your/local/path/bullet_train-base/app/views/layouts/devise.html.erb -->".blue
169
+ exit
170
+ end
171
+ end
172
+
126
173
  begin
127
174
  annotated_path = ApplicationController.render(template: "bullet_train/partial_resolver", layout: nil, assigns: {needle: @needle}).lines[1].chomp
128
175
  rescue ActionView::Template::Error => e
@@ -1,3 +1,3 @@
1
1
  module BulletTrain
2
- VERSION = "1.1.10"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/bullet_train.rb CHANGED
@@ -53,8 +53,13 @@ def default_url_options_from_base_url
53
53
 
54
54
  # the name of this property doesn't match up.
55
55
  default_url_options[:protocol] = parsed_base_url.scheme
56
+ default_url_options.compact!
56
57
 
57
- default_url_options.compact
58
+ if default_url_options.empty?
59
+ raise "ENV['BASE_URL'] has not been configured correctly. Please check your environment variables and try one more time."
60
+ end
61
+
62
+ default_url_options
58
63
  end
59
64
 
60
65
  def inbound_email_enabled?
@@ -59,7 +59,16 @@ namespace :bullet_train do
59
59
  if ARGV.first.present?
60
60
  BulletTrain::Resolver.new(ARGV.first).run(eject: ARGV.include?("--eject"), open: ARGV.include?("--open"), force: ARGV.include?("--force"), interactive: ARGV.include?("--interactive"))
61
61
  else
62
- warn "\nšŸš… Usage: `bin/resolve [path, partial, or URL] (--eject) (--open)`\n".blue
62
+ warn <<~MSG
63
+ šŸš… Usage: #{"`bin/resolve [path, partial, or URL] (--eject) (--open)`".blue}
64
+
65
+ OR
66
+
67
+ #{"`bin/resolve --interactive`".blue}
68
+ When you use the interactive flag, we will prompt you to pass an annotated partial like so and either eject or open the file.
69
+ These annotated paths can be found in your browser when inspecting elements:
70
+ <!-- BEGIN /your/path/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bullet_train-themes-light-1.0.51/app/views/themes/light/_notices.html.erb -->
71
+ MSG
63
72
  end
64
73
  end
65
74
 
@@ -82,6 +91,55 @@ namespace :bullet_train do
82
91
  puts ""
83
92
  end
84
93
 
94
+ # Process any flags that were passed.
95
+ if arguments[:all_options].present?
96
+ flags_with_values = []
97
+
98
+ arguments[:all_options].split(/\s+/).each do |option|
99
+ if option.match?(/^--/)
100
+ flags_with_values << {flag: option.gsub(/^--/, "").to_sym, values: []}
101
+ else
102
+ flags_with_values.last[:values] << option
103
+ end
104
+ end
105
+
106
+ if flags_with_values.any?
107
+ flags_with_values.each do |process|
108
+ if process[:flag] == :link || process[:flag] == :reset
109
+ packages = process[:values]
110
+
111
+ gemfile_lines = File.readlines("./Gemfile")
112
+ new_lines = gemfile_lines.map do |line|
113
+ packages.each do |package|
114
+ if line.match?(package)
115
+ original_path = "gem \"bullet_train#{"-" + package unless package == "base"}\""
116
+ local_path = "gem \"bullet_train#{"-" + package unless package == "base"}\", path: \"local/bullet_train-#{package}\""
117
+
118
+ case process[:flag]
119
+ when :link
120
+ line.gsub!(original_path, local_path)
121
+ puts "Setting local '#{package}' package to the Gemfile...".blue
122
+ break
123
+ when :reset
124
+ line.gsub!(local_path, original_path)
125
+ puts "Resetting '#{package}' package in the Gemfile...".blue
126
+ break
127
+ end
128
+ end
129
+ end
130
+
131
+ line
132
+ end
133
+
134
+ File.write("./Gemfile", new_lines.join)
135
+ system "bundle install"
136
+ end
137
+ end
138
+
139
+ exit
140
+ end
141
+ end
142
+
85
143
  framework_packages = I18n.t("framework_packages")
86
144
 
87
145
  puts "Which framework package do you want to work on?".blue
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.1.10
4
+ version: 1.2.0
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-12-05 00:00:00.000000000 Z
11
+ date: 2022-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: standard
@@ -522,6 +522,7 @@ files:
522
522
  - app/views/account/memberships/_fields.html.erb
523
523
  - app/views/account/memberships/_form.html.erb
524
524
  - app/views/account/memberships/_index.html.erb
525
+ - app/views/account/memberships/_membership.html.erb
525
526
  - app/views/account/memberships/_menu_item.html.erb
526
527
  - app/views/account/memberships/_tombstones.html.erb
527
528
  - app/views/account/memberships/edit.html.erb
@@ -534,6 +535,7 @@ files:
534
535
  - app/views/account/teams/_form.html.erb
535
536
  - app/views/account/teams/_index.html.erb
536
537
  - app/views/account/teams/_menu_item.html.erb
538
+ - app/views/account/teams/_team.html.erb
537
539
  - app/views/account/teams/_team.json.jbuilder
538
540
  - app/views/account/teams/edit.html.erb
539
541
  - app/views/account/teams/index.html.erb
@@ -624,12 +626,14 @@ files:
624
626
  - db/migrate/20211020200855_add_doorkeeper_application_to_memberships.rb
625
627
  - db/migrate/20211027002944_add_doorkeeper_application_to_users.rb
626
628
  - docs/action-models.md
629
+ - docs/application-options.md
627
630
  - docs/authentication.md
628
631
  - docs/billing/stripe.md
629
632
  - docs/billing/usage.md
630
633
  - docs/desktop.md
631
634
  - docs/field-partials.md
632
635
  - docs/field-partials/buttons.md
636
+ - docs/field-partials/file-field.md
633
637
  - docs/field-partials/super-select.md
634
638
  - docs/font-awesome-pro.md
635
639
  - docs/getting-started.md
@@ -652,7 +656,9 @@ files:
652
656
  - docs/teams.md
653
657
  - docs/testing.md
654
658
  - docs/themes.md
659
+ - docs/trademark.md
655
660
  - docs/tunneling.md
661
+ - docs/two-factor-authentication.md
656
662
  - docs/upgrades.md
657
663
  - docs/webhooks/incoming.md
658
664
  - docs/webhooks/outgoing.md