bullet_train 1.1.3 → 1.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 070546e816ec7e8e957ea242a592248815b52c2c82694106860e9d35f6c8513e
4
- data.tar.gz: ddaab3ea27327a6fc10bc75d2e05a4134c21241444b0f2334c1954da92838289
3
+ metadata.gz: a08595be0c53652a5cd5dfeeb920bc080476a31b180bb4832919398bcb0e2bf0
4
+ data.tar.gz: 8132b225ed537f17fd13c17a8d6edc4c99b63712d56cd208a5fda981074e819d
5
5
  SHA512:
6
- metadata.gz: c553f5262f78742fffbae7c11fcd5915671b965d1751887edc907e997768e0f04c4a9b36a44c9b0737bb303398b2bdc1c9839732f5848a67ca2282211f7b93d7
7
- data.tar.gz: dbf6c71556ffd300279c92604d5c347bd3b631a298668251d1676344c6c7564c5d08f6655429df447014010e7d5765acc1abfab82524a9ac557d24ff1761eeca
6
+ metadata.gz: 2f6b84f79d914c69c184c1e0e8c9213eb0b8a000fcfbdc017768f5876408f99ec4a00c481e14c7f21ff9bc3de9652847c6ca37378e0446e153dbea918494bb94
7
+ data.tar.gz: 7d59102fd98d4b0fc7877be50b40285d4ef1597d694242e70b70e4d8807638220bbad1232ada6edafd8ab968767d3012ad78a1f7f2347ef220bf911a7447ce2c
@@ -84,11 +84,16 @@ module Account::Memberships::ControllerBase
84
84
  end
85
85
 
86
86
  def reinvite
87
- @invitation = Invitation.new(membership: @membership, team: @team, email: @membership.user_email, from_membership: current_membership)
88
- if @invitation.save
89
- redirect_to [:account, @team, :memberships], notice: I18n.t("account.memberships.notifications.reinvited")
87
+ if helpers.current_limits.can?(:create, Membership)
88
+ @invitation = Invitation.new(membership: @membership, team: @team, email: @membership.user_email, from_membership: current_membership)
89
+ if @invitation.save
90
+ redirect_to [:account, @team, :memberships], notice: I18n.t("account.memberships.notifications.reinvited")
91
+ else
92
+ redirect_to [:account, @team, :memberships], notice: "There was an error creating the invitation (#{@invitation.errors.full_messages.to_sentence})"
93
+ end
90
94
  else
91
- redirect_to [:account, @team, :memberships], notice: "There was an error creating the invitation (#{@invitation.errors.full_messages.to_sentence})"
95
+ flash[:error] = :create_limit
96
+ redirect_to [:account, @team, :memberships]
92
97
  end
93
98
  end
94
99
 
@@ -1,5 +1,7 @@
1
1
  module Account::TeamsHelper
2
2
  def current_team
3
+ # TODO We do not want this to be based on the `current_team_id`.
4
+ # TODO We want this to be based on the current resource being loaded.
3
5
  current_user&.current_team
4
6
  end
5
7
 
@@ -77,4 +79,12 @@ module Account::TeamsHelper
77
79
  def can_invite?
78
80
  can?(:create, Invitation.new(team: current_team))
79
81
  end
82
+
83
+ def current_limits
84
+ @limiter ||= if billing_enabled? && defined?(Billing::Limiter)
85
+ Billing::Limiter.new(current_team)
86
+ else
87
+ Billing::MockLimiter.new(current_team)
88
+ end
89
+ end
80
90
  end
@@ -0,0 +1,9 @@
1
+ class Billing::MockLimiter
2
+ def broken_hard_limits_for(action, model, count: 1)
3
+ []
4
+ end
5
+
6
+ def can?(action, model)
7
+ true
8
+ end
9
+ end
@@ -31,7 +31,7 @@ module Memberships::Base
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?
34
- scope :billable, -> { current }
34
+ scope :billable, -> { current_and_invited }
35
35
  end
36
36
  end
37
37
 
@@ -4,6 +4,10 @@ module Records::Base
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
+ if billing_enabled? && defined?(Billing::UsageSupport)
8
+ include Billing::UsageSupport
9
+ end
10
+
7
11
  if defined?(Webhooks::Outgoing::IssuingModel)
8
12
  include Webhooks::Outgoing::IssuingModel
9
13
  end
@@ -27,6 +27,10 @@ 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
30
34
  end
31
35
 
32
36
  # validations
@@ -62,6 +66,12 @@ module Teams::Base
62
66
 
63
67
  # TODO Probably we can provide a way for gem packages to define these kinds of extensions.
64
68
  if billing_enabled?
69
+ def current_billing_subscription
70
+ # If by some bug we have two subscriptions, we want to use the one that existed first.
71
+ # The reasoning here is that it's more likely to be on some legacy plan that benefits the customer.
72
+ billing_subscriptions.active.order(:created_at).first
73
+ end
74
+
65
75
  def needs_billing_subscription?
66
76
  return false if freemium_enabled?
67
77
  billing_subscriptions.active.empty?
@@ -1,7 +1,7 @@
1
1
  <% invitation ||= @invitation %>
2
2
  <% team ||= @team || invitation&.team %>
3
3
  <%= render 'account/teams/breadcrumbs', team: team %>
4
- <%= render 'account/shared/breadcrumb', label: t('.label'), url: [:account, team, :memberships] %>
4
+ <%= render 'account/shared/breadcrumb', label: t('memberships.label'), url: [:account, team, :memberships] %>
5
5
  <%= render 'account/shared/breadcrumb', label: t('.label'), url: [:account, team, :invitations] %>
6
6
  <% if invitation&.persisted? %>
7
7
  <%= render 'account/shared/breadcrumb', label: invitation.label_string, url: [:account, invitation] %>
@@ -1,49 +1,51 @@
1
1
  <%= form_with(model: [:account, (@team unless invitation.persisted?), invitation], class: 'form', local: true) do |form| %>
2
- <%= render 'account/shared/forms/errors', form: form %>
2
+ <%= render "shared/limits/form", form: form, model: invitation.membership, cancel_path: @cancel_path || account_invitation_path(invitation) do %>
3
+ <%= render 'account/shared/forms/errors', form: form %>
3
4
 
4
- <%= render 'shared/fields/email_field', form: form, method: :email, options: {autofocus: true} %>
5
+ <%= render 'shared/fields/email_field', form: form, method: :email, options: {autofocus: true} %>
5
6
 
6
- <%= form.fields_for :membership do |membership_form| %>
7
- <div class="grid grid-cols-1 gap-y gap-x sm:grid-cols-6">
8
- <div class="sm:col-span-3">
9
- <%= render 'shared/fields/text_field', form: membership_form, method: :user_first_name %>
10
- </div>
7
+ <%= form.fields_for :membership do |membership_form| %>
8
+ <div class="grid grid-cols-1 gap-y gap-x sm:grid-cols-6">
9
+ <div class="sm:col-span-3">
10
+ <%= render 'shared/fields/text_field', form: membership_form, method: :user_first_name %>
11
+ </div>
11
12
 
12
- <div class="sm:col-span-3">
13
- <%= render 'shared/fields/text_field', form: membership_form, method: :user_last_name %>
13
+ <div class="sm:col-span-3">
14
+ <%= render 'shared/fields/text_field', form: membership_form, method: :user_last_name %>
15
+ </div>
14
16
  </div>
15
- </div>
16
- <% end %>
17
+ <% end %>
17
18
 
18
- <% if can? :manage, @team %>
19
- <%= form.fields_for :membership do |fields| %>
20
- <%= fields.hidden_field :team_id, value: @team.id %>
21
- <div class="space-y-3">
22
- <% Membership.assignable_roles.each do |role| %>
23
- <% if current_membership.can_manage_role?(role) %>
24
- <div class="flex items-top">
25
- <%= fields.check_box :role_ids, {multiple: true, class: "h-4 w-4 text-blue focus:ring-blue-dark border-gray-300 rounded mt-0.5"}, role.id, nil %>
26
- <label for="invitation_membership_attributes_role_ids_<%= role.id %>" class="ml-2 block select-none">
27
- <span><%= t('invitations.form.invite_as', role_key: t("memberships.fields.role_ids.options.#{role.key}.label")) %></span>
28
- <div class="mt-0.5 text-gray-400 font-light leading-normal">
29
- <%= t("memberships.fields.role_ids.options.#{role.key}.description") %>
30
- </div>
31
- </label>
32
- </div>
19
+ <% if can? :manage, @team %>
20
+ <%= form.fields_for :membership do |fields| %>
21
+ <%= fields.hidden_field :team_id, value: @team.id %>
22
+ <div class="space-y-3">
23
+ <% Membership.assignable_roles.each do |role| %>
24
+ <% if current_membership.can_manage_role?(role) %>
25
+ <div class="flex items-top">
26
+ <%= fields.check_box :role_ids, {multiple: true, class: "h-4 w-4 text-blue focus:ring-blue-dark border-gray-300 rounded mt-0.5"}, role.id, nil %>
27
+ <label for="invitation_membership_attributes_role_ids_<%= role.id %>" class="ml-2 block select-none">
28
+ <span><%= t('invitations.form.invite_as', role_key: t("memberships.fields.role_ids.options.#{role.key}.label")) %></span>
29
+ <div class="mt-0.5 text-gray-400 font-light leading-normal">
30
+ <%= t("memberships.fields.role_ids.options.#{role.key}.description") %>
31
+ </div>
32
+ </label>
33
+ </div>
34
+ <% end %>
33
35
  <% end %>
34
- <% end %>
35
- </div>
36
+ </div>
37
+ <% end %>
36
38
  <% end %>
37
- <% end %>
38
39
 
39
- <%# 🚅 super scaffolding will insert new fields above this line. %>
40
+ <%# 🚅 super scaffolding will insert new fields above this line. %>
40
41
 
41
- <div class="buttons">
42
- <%= form.submit (form.object.persisted? ? t('.buttons.update') : t('.buttons.create')), class: "button" %>
43
- <% if form.object.persisted? %>
44
- <%= link_to t('global.buttons.cancel'), account_invitation_path(invitation), class: "button-secondary" %>
45
- <% else %>
46
- <%= link_to t('global.buttons.cancel'), @cancel_path || account_team_invitations_path(@team), class: "button-secondary" %>
47
- <% end %>
48
- </div>
42
+ <div class="buttons">
43
+ <%= form.submit (form.object.persisted? ? t('.buttons.update') : t('.buttons.create')), class: "button" %>
44
+ <% if form.object.persisted? %>
45
+ <%= link_to t('global.buttons.cancel'), account_invitation_path(invitation), class: "button-secondary" %>
46
+ <% else %>
47
+ <%= link_to t('global.buttons.cancel'), @cancel_path || account_team_invitations_path(@team), class: "button-secondary" %>
48
+ <% end %>
49
+ </div>
50
+ <% end %>
49
51
  <% end %>
@@ -42,5 +42,4 @@
42
42
  <%= form.submit t('.buttons.update'), class: "button" %>
43
43
  <%= link_to t('global.buttons.cancel'), [:account, @team, :memberships], class: "button-secondary" %>
44
44
  </div>
45
-
46
45
  <% end %>
@@ -2,65 +2,68 @@
2
2
  <% hide_actions ||= false %>
3
3
  <% hide_back ||= false %>
4
4
 
5
- <%= render 'account/shared/box' do |p| %>
6
- <% p.content_for :title, t(".contexts.#{context.class.name.underscore}.header") %>
7
- <% p.content_for :description do %>
8
- <%= raw t(".contexts.#{context.class.name.underscore}.#{memberships.any? ? 'description' : 'description_empty'}") %>
9
- <% end %>
5
+ <%= updates_for context, :memberships do %>
6
+ <%= render 'account/shared/box' do |p| %>
7
+ <% p.content_for :title, t(".contexts.#{context.class.name.underscore}.header") %>
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 %>
11
+ <% end %>
10
12
 
11
- <% p.content_for :table do %>
12
- <% if memberships.any? %>
13
- <table class="table">
14
- <thead>
15
- <tr>
16
- <th><%= t('memberships.singular') %></th>
17
- <th><%= t('memberships.fields.role_ids.heading') %></th>
18
- <%# 🚅 super scaffolding will insert new field headers above this line. %>
19
- <th></th>
20
- </tr>
21
- </thead>
22
- <tbody data-model="Membership" data-scope="current">
23
- <% memberships.each do |membership| %>
24
- <tr data-id="<%= membership.id %>">
13
+ <% p.content_for :table do %>
14
+ <% if memberships.any? %>
15
+ <table class="table">
16
+ <thead>
17
+ <tr>
18
+ <th><%= t('memberships.singular') %></th>
19
+ <th><%= t('memberships.fields.role_ids.heading') %></th>
20
+ <%# 🚅 super scaffolding will insert new field headers above this line. %>
21
+ <th></th>
22
+ </tr>
23
+ </thead>
24
+ <tbody data-model="Membership" data-scope="current">
25
+ <% memberships.each do |membership| %>
26
+ <tr data-id="<%= membership.id %>">
25
27
 
26
- <td class="px-6 py-4 whitespace-nowrap">
27
- <%= link_to [:account, membership], class: 'block flex items-center group hover:no-underline no-underline' do %>
28
- <div class="flex-shrink-0 h-10 w-10">
29
- <%= image_tag membership_profile_photo_url(membership), title: membership.label_string, class: 'h-10 w-10 rounded-full' %>
30
- </div>
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>
31
33
 
32
- <div class="ml-3">
33
- <span class="group-hover:underline"><%= membership.label_string %></span>
34
- <% if membership.unclaimed? %>
35
- <span class="ml-1.5 px-2 inline-flex text-xs text-green-dark bg-green-light border border-green-dark rounded-md">
36
- Invited
37
- </span>
38
- <% end %>
39
- </div>
40
- <% end %>
41
- </td>
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>
42
44
 
43
- <td>
44
- <% if membership.roles_without_defaults.any? %>
45
- <%= membership.roles_without_defaults.map { |role| t("memberships.fields.role_ids.options.#{role.key}.label") }.to_sentence %>
46
- <% else %>
47
- <%= t("memberships.fields.role_ids.options.default.label") %>
48
- <% end %>
49
- </td>
50
- <td class="text-right">
51
- <%= link_to t('.buttons.show'), [:account, membership], class: 'button-secondary button-smaller' %>
52
- </td>
53
- </tr>
54
- <% end %>
55
- </tbody>
56
- </table>
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 %>
57
+ </tbody>
58
+ </table>
59
+ <% end %>
57
60
  <% end %>
58
- <% end %>
59
61
 
60
- <% unless hide_actions %>
61
- <% p.content_for :actions do %>
62
- <%= link_to t('invitations.buttons.new'), new_account_team_invitation_path(@team, cancel_path: account_team_memberships_path(@team)), class: "#{first_button_primary}" %>
63
- <%= link_to t('global.buttons.back'), [:account, context], class: "#{first_button_primary} back" unless hide_back %>
62
+ <% unless hide_actions %>
63
+ <% p.content_for :actions do %>
64
+ <%= link_to t('invitations.buttons.new'), new_account_team_invitation_path(@team, cancel_path: account_team_memberships_path(@team)), class: "#{first_button_primary}" %>
65
+ <%= link_to t('global.buttons.back'), [:account, context], class: "#{first_button_primary} back" unless hide_back %>
66
+ <% end %>
64
67
  <% end %>
65
68
  <% end %>
66
69
  <% end %>
@@ -60,8 +60,11 @@ en:
60
60
  heading: *locale
61
61
  # 🚅 super scaffolding will insert new fields above this line.
62
62
  created_at:
63
- _: &created_at Created
63
+ _: &created_at Signed Up At
64
64
  heading: *created_at
65
+ updated_at:
66
+ _: &updated_at Updated At
67
+ heading: *updated_at
65
68
  _: &self
66
69
  name:
67
70
  label: Your Team Name
@@ -84,3 +87,4 @@ en:
84
87
  locale: *locale
85
88
  # 🚅 super scaffolding will insert new activerecord attributes above this line.
86
89
  created_at: *created_at
90
+ updated_at: *updated_at
@@ -27,6 +27,10 @@ en:
27
27
  notifications:
28
28
  updated: User was successfully updated.
29
29
  fields: &fields
30
+ id:
31
+ _: &id Team ID
32
+ label: *id
33
+ heading: *id
30
34
  name:
31
35
  heading: Name
32
36
  first_name:
@@ -64,8 +68,11 @@ en:
64
68
  help: By default the interface language will adjust based on each team's language setting, but you can set a global personal preference for your account here.
65
69
  # 🚅 super scaffolding will insert new fields above this line.
66
70
  created_at:
67
- _: &created_at Signed Up
71
+ _: &created_at Signed Up At
68
72
  heading: *created_at
73
+ updated_at:
74
+ _: &updated_at Updated At
75
+ heading: *updated_at
69
76
  # this is how we define customizations to fields for a specific namespace.
70
77
  _: &self
71
78
  email:
@@ -105,6 +112,7 @@ en:
105
112
  time_zone: *time_zone
106
113
  locale: *locale
107
114
  # 🚅 super scaffolding will insert new activerecord attributes above this line.
108
- created_at: *created_at
109
115
  password: *password
110
116
  password_confirmation: *password_confirmation
117
+ created_at: *created_at
118
+ updated_at: *updated_at
@@ -1,3 +1,3 @@
1
1
  module BulletTrain
2
- VERSION = "1.1.3"
2
+ VERSION = "1.1.5"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet_train
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.1.5
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-09-05 00:00:00.000000000 Z
11
+ date: 2022-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: standard
@@ -483,6 +483,7 @@ files:
483
483
  - app/mailers/concerns/mailers/base.rb
484
484
  - app/mailers/devise_mailer.rb
485
485
  - app/mailers/user_mailer.rb
486
+ - app/models/billing/mock_limiter.rb
486
487
  - app/models/concerns/current_attributes/base.rb
487
488
  - app/models/concerns/invitations/base.rb
488
489
  - app/models/concerns/memberships/base.rb