bullet_train 1.1.3 → 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
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