bullet_train 1.0.76 → 1.0.83

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: 1304c0985b95c7d97ec6946b7db2cba4aa6ff9c9cd94d42b5235ecfff87df984
4
- data.tar.gz: a06bdab53e17c60fdb793cd65bd4ccc66f07eec005a36049566d51913352672b
3
+ metadata.gz: d2119a17f5ed1aace492642ab674c8fb67cadefd29100aee3809e590813da3bb
4
+ data.tar.gz: '0679c15de2ec6260be9beab3c1261fbef4892cacd961d285f813229aac5b1616'
5
5
  SHA512:
6
- metadata.gz: 0645f429cf77724cebe323c67cfbb99c9da602eee2f1c66df4de8c80207a5740bf555842f79bc0a74c56eb40328af2780b9f7dab3ccf81dea37ac7b72807b63d
7
- data.tar.gz: ad960c79b67b759d1e25d9a4d61fd8edb87ff08ea5f08b82fc29c1bc7d3f06d14bf80b390739226362015dd3c6fe540432cf8f6971b9a314a4806a7099f3ea93
6
+ metadata.gz: 807684f8b6d5a99a6d35a78d0e50cbeb8a0ef40d06b1e56c1c653a6851ea0289e50c75f2a1e75a1500f6799f3fcd91a603fffaa64ea12324068a851290e67186
7
+ data.tar.gz: 964b44f5ddaa872e26e8cefcf0dd6f9a11d19fc7d3b435e10e7a14d6f62c420a60945dde1eb0c9c588f049cd64423e93c82c52e2b207c01c2cfbe7d58c5b125b
@@ -5,6 +5,10 @@ module Account::Controllers::Base
5
5
  include LoadsAndAuthorizesResource
6
6
  include Fields::ControllerSupport
7
7
 
8
+ if billing_enabled?
9
+ include Billing::ControllerSupport
10
+ end
11
+
8
12
  before_action :set_last_seen_at, if: proc {
9
13
  user_signed_in? && (current_user.last_seen_at.nil? || current_user.last_seen_at < 1.minute.ago)
10
14
  }
@@ -106,6 +110,11 @@ module Account::Controllers::Base
106
110
  end
107
111
  end
108
112
 
113
+ # TODO Maybe in this context we should check whether `Billing::ControllerSupport` is included instead of just defined?
114
+ if defined?(Billing::ControllerSupport)
115
+ enforce_billing_requirements
116
+ # See `app/controllers/concerns/billing_support.rb` for details.
117
+ end
109
118
  end
110
119
 
111
120
  true
@@ -81,7 +81,7 @@ module Account::Teams::ControllerBase
81
81
  format.html { redirect_to [:account, @team], notice: I18n.t("teams.notifications.created") }
82
82
  format.json { render :show, status: :created, location: [:account, @team] }
83
83
  else
84
- format.html { render :new, layout: "devise" }
84
+ format.html { render :new, layout: "devise", status: :unprocessable_entity }
85
85
  format.json { render json: @team.errors, status: :unprocessable_entity }
86
86
  end
87
87
  end
@@ -1,6 +1,10 @@
1
1
  module BaseHelper
2
2
  # TODO This is for the billing package to override, but I feel like there has got to be a better way to do this.
3
3
  def hide_team_resource_menus?
4
- false
4
+ if billing_enabled?
5
+ current_team.needs_billing_subscription?
6
+ else
7
+ false
8
+ end
5
9
  end
6
10
  end
@@ -29,6 +29,11 @@ module Memberships::Base
29
29
  scope :current_and_invited, -> { includes(:invitation).where("user_id IS NOT NULL OR invitations.id IS NOT NULL").references(:invitation) }
30
30
  scope :current, -> { where("user_id IS NOT NULL") }
31
31
  scope :tombstones, -> { includes(:invitation).where("user_id IS NULL AND invitations.id IS NULL").references(:invitation) }
32
+
33
+ # TODO Probably we can provide a way for gem packages to define these kinds of extensions.
34
+ if billing_enabled?
35
+ scope :billable, -> { current }
36
+ end
32
37
  end
33
38
 
34
39
  def name
@@ -28,6 +28,14 @@ module Records::Base
28
28
  scope :newest_updated, -> { order("updated_at DESC") }
29
29
  scope :oldest_updated, -> { order("updated_at ASC") }
30
30
 
31
+ # TODO Probably we can provide a way for gem packages to define these kinds of extensions.
32
+ if billing_enabled?
33
+ # By default, any model in a collection is considered active for billing purposes.
34
+ # This can be overloaded in the child model class to specify more specific criteria for billing.
35
+ # See `app/models/concerns/memberships/base.rb` for an example.
36
+ scope :billable, -> { order("TRUE") }
37
+ end
38
+
31
39
  # Microscope adds useful scopes targeting ActiveRecord `boolean`, `date` and `datetime` attributes.
32
40
  # https://github.com/mirego/microscope
33
41
  acts_as_microscope
@@ -18,6 +18,17 @@ module Teams::Base
18
18
  # integrations
19
19
  has_many :integrations_stripe_installations, class_name: "Integrations::StripeInstallation", dependent: :destroy if stripe_enabled?
20
20
 
21
+ # TODO Probably we can provide a way for gem packages to define these kinds of extensions.
22
+ if billing_enabled?
23
+ # subscriptions
24
+ has_many :billing_subscriptions, class_name: "Billing::Subscription", dependent: :destroy, foreign_key: :team_id
25
+
26
+ # TODO We need a way for `bullet_train-billing-stripe` to define these.
27
+ if defined?(Billing::Stripe::Subscription)
28
+ has_many :billing_stripe_subscriptions, class_name: "Billing::Stripe::Subscription", dependent: :destroy, foreign_key: :team_id
29
+ end
30
+ end
31
+
21
32
  # validations
22
33
  validates :name, presence: true
23
34
  validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
@@ -48,4 +59,12 @@ module Teams::Base
48
59
  # generic functions need to function for a team model as well, so we do this.
49
60
  self
50
61
  end
62
+
63
+ # TODO Probably we can provide a way for gem packages to define these kinds of extensions.
64
+ if billing_enabled?
65
+ def needs_billing_subscription?
66
+ return false if freemium_enabled?
67
+ billing_subscriptions.active.empty?
68
+ end
69
+ end
51
70
  end
@@ -0,0 +1,17 @@
1
+ en:
2
+ billing/products:
3
+ free:
4
+ name: Free
5
+ basic:
6
+ name: Basic
7
+ description: This is an example plan that includes a free trial when paid for monthly.
8
+ features:
9
+ - Demonstrates pricing per team member.
10
+ - Allows creation of up to fifty "Creative Concepts".
11
+ - Soft enforcement that limit.
12
+ pro:
13
+ name: Pro
14
+ description: An improved example plan that demonstrates different features.
15
+ features:
16
+ - Demonstrates a fixed price for up to ten team members.
17
+ - Allows creation of an unlimited number of "Creative Concepts".
@@ -1,3 +1,3 @@
1
1
  module BulletTrain
2
- VERSION = "1.0.76"
2
+ VERSION = "1.0.83"
3
3
  end
data/lib/bullet_train.rb CHANGED
@@ -61,7 +61,12 @@ def inbound_email_enabled?
61
61
  ENV["INBOUND_EMAIL_DOMAIN"].present?
62
62
  end
63
63
 
64
- def subscriptions_enabled?
64
+ def billing_enabled?
65
+ defined?(BulletTrain::Billing)
66
+ end
67
+
68
+ # TODO This should be in an initializer or something.
69
+ def billing_subscription_creation_disabled?
65
70
  false
66
71
  end
67
72
 
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.0.76
4
+ version: 1.0.83
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-06-15 00:00:00.000000000 Z
11
+ date: 2022-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: standard
@@ -428,8 +428,6 @@ files:
428
428
  - README.md
429
429
  - Rakefile
430
430
  - app/assets/config/bullet_train_manifest.js
431
- - app/assets/javascripts/bullet-train.js
432
- - app/assets/javascripts/bullet-train.js.map
433
431
  - app/controllers/account/invitations_controller.rb
434
432
  - app/controllers/account/memberships_controller.rb
435
433
  - app/controllers/account/onboarding/user_details_controller.rb
@@ -558,6 +556,7 @@ files:
558
556
  - config/initializers/concerns/inflections_base.rb
559
557
  - config/initializers/concerns/turbo_failure_app.rb
560
558
  - config/locales/en/base.yml
559
+ - config/locales/en/billing/products.en.yml
561
560
  - config/locales/en/devise.en.yml
562
561
  - config/locales/en/doorkeeper.en.yml
563
562
  - config/locales/en/invitations.en.yml
@@ -667,7 +666,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
667
666
  - !ruby/object:Gem::Version
668
667
  version: '0'
669
668
  requirements: []
670
- rubygems_version: 3.3.7
669
+ rubygems_version: 3.2.22
671
670
  signing_key:
672
671
  specification_version: 4
673
672
  summary: Bullet Train
@@ -1,2 +0,0 @@
1
- import{Controller as e}from"@hotwired/stimulus";function t(e){const t=(e.match(/^(?:\.\/)?(.+)(?:[_-]controller\..+?)$/)||[])[1];if(t)return t.replace(/_/g,"-").replace(/\//g,"--")}class i extends e{copy(){this.inputTarget.value=this.sourceTarget.innerText,this.inputTarget.select(),document.execCommand("copy"),this.buttonTarget.innerHTML='<i id="copied" class="fas fa-check w-4 h-4 block text-green-600"></i>',setTimeout(function(){document.getElementById("copied").innerHTML='<i class="far fa-copy w-4 h-4 block text-gray-600"></i>'},1500)}}i.targets=["source","input","button"];class r extends e{constructor(){super(...arguments),this.removeTrailingNewlines=e=>{e.element.innerHTML.match(/<br><\/div>$/)&&(e.element.innerHTML=e.element.innerHTML.slice(0,-10)+"</div>",this.removeTrailingNewlines(e))},this.removeTrailingWhitespace=e=>{e.element.innerHTML.match(/&nbsp;<\/div>$/)?(e.element.innerHTML=e.element.innerHTML.slice(0,-12)+"</div>",this.removeTrailingWhitespace(e)):e.element.innerHTML.match(/&nbsp; <\/div>$/)&&(e.element.innerHTML=e.element.innerHTML.slice(0,-13)+"</div>",this.removeTrailingWhitespace(e))}}resetOnSuccess(e){e.detail.success&&e.target.reset()}stripTrix(){this.trixFieldTargets.forEach(e=>{this.removeTrailingNewlines(e.editor),this.removeTrailingWhitespace(e.editor),e.parentElement.querySelector("input").value=e.innerHTML})}submitOnReturn(e){if((e.metaKey||e.ctrlKey)&&13==e.keyCode){e.preventDefault();let t=e.target.closest("form");this.submitForm(t)}}submitForm(e){e.requestSubmit?e.requestSubmit():e.querySelector("[type=submit]").click()}}r.targets=["trixField","scroll"];class s extends e{toggle(){const e=this.isWrapperHidden?this.showEventNameValue:this.hideEventNameValue;this.isWrapperHidden&&this.showWrapper(),this.wrapperTarget.dispatchEvent(new CustomEvent(e))}get isWrapperHidden(){return this.wrapperTarget.classList.contains(this.hiddenClass)}showWrapper(){this.wrapperTarget.classList.remove(this.hiddenClass)}hideWrapper(){this.wrapperTarget.classList.add(this.hiddenClass)}}s.targets=["wrapper"],s.classes=["hidden"],s.values={showEventName:String,hideEventName:String};const n=[[i,"clipboard_controller.js"],[r,"form_controller.js"],[s,"mobile_menu_controller.js"]].map(function(e){const i=e[0];return{identifier:t(e[1]),controllerConstructor:i}});document.addEventListener("turbo:load",()=>{navigator.userAgent.toLocaleLowerCase().includes("electron")&&document.body.classList.add("electron")});export{n as controllerDefinitions};
2
- //# sourceMappingURL=bullet-train.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bullet-train.js","sources":["../../../node_modules/@hotwired/stimulus-webpack-helpers/dist/stimulus-webpack-helpers.js","../../javascript/controllers/clipboard_controller.js","../../javascript/controllers/form_controller.js","../../javascript/controllers/mobile_menu_controller.js","../../javascript/controllers/index.js","../../javascript/electron/index.js"],"sourcesContent":["/*\nStimulus Webpack Helpers 1.0.0\nCopyright © 2021 Basecamp, LLC\n */\nfunction definitionsFromContext(context) {\n return context.keys()\n .map((key) => definitionForModuleWithContextAndKey(context, key))\n .filter((value) => value);\n}\nfunction definitionForModuleWithContextAndKey(context, key) {\n const identifier = identifierForContextKey(key);\n if (identifier) {\n return definitionForModuleAndIdentifier(context(key), identifier);\n }\n}\nfunction definitionForModuleAndIdentifier(module, identifier) {\n const controllerConstructor = module.default;\n if (typeof controllerConstructor == \"function\") {\n return { identifier, controllerConstructor };\n }\n}\nfunction identifierForContextKey(key) {\n const logicalName = (key.match(/^(?:\\.\\/)?(.+)(?:[_-]controller\\..+?)$/) || [])[1];\n if (logicalName) {\n return logicalName.replace(/_/g, \"-\").replace(/\\//g, \"--\");\n }\n}\n\nexport { definitionForModuleAndIdentifier, definitionForModuleWithContextAndKey, definitionsFromContext, identifierForContextKey };\n","import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n static targets = ['source', 'input', 'button']\n\n copy() {\n this.inputTarget.value = this.sourceTarget.innerText\n this.inputTarget.select()\n document.execCommand('copy')\n this.buttonTarget.innerHTML = '<i id=\"copied\" class=\"fas fa-check w-4 h-4 block text-green-600\"></i>'\n setTimeout(function () {\n document.getElementById('copied').innerHTML = '<i class=\"far fa-copy w-4 h-4 block text-gray-600\"></i>'\n }, 1500)\n }\n}\n","import { Controller } from \"@hotwired/stimulus\"\n\n// TODO Some of this feels really specific to the conversation messages form. Should we rename this controller?\nexport default class extends Controller {\n static targets = ['trixField', 'scroll']\n\n resetOnSuccess(e){\n if(e.detail.success) {\n e.target.reset();\n }\n }\n\n stripTrix(){\n this.trixFieldTargets.forEach(element => {\n this.removeTrailingNewlines(element.editor)\n this.removeTrailingWhitespace(element.editor)\n // When doing this as part of the form submission, Trix does not update the input element's value attribute fast enough.\n // In order to submit the stripped value, we manually update it here to fix the race condition\n element.parentElement.querySelector(\"input\").value = element.innerHTML\n })\n }\n\n submitOnReturn(e) {\n if((e.metaKey || e.ctrlKey) && e.keyCode == 13) {\n e.preventDefault();\n let form = e.target.closest(\"form\")\n this.submitForm(form)\n }\n }\n\n removeTrailingNewlines = (trixEditor) => {\n if (trixEditor.element.innerHTML.match(/<br><\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -10) + \"</div>\"\n this.removeTrailingNewlines(trixEditor)\n }\n }\n\n removeTrailingWhitespace = (trixEditor) => {\n if (trixEditor.element.innerHTML.match(/&nbsp;<\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -12) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n } else if (trixEditor.element.innerHTML.match(/&nbsp; <\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -13) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n }\n }\n\n submitForm(form) {\n // Right now, Safari and IE don't support the requestSubmit method which is required for Turbo\n // Doing form.submit() doesn't actually fire the submit event which Turbo needs\n if (form.requestSubmit) {\n form.requestSubmit()\n } else {\n form.querySelector(\"[type=submit]\").click()\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n static targets = [ \"wrapper\"]\n static classes = [ \"hidden\" ] // necessary because stimulus-reveal will mess with the [hidden] attribute on the wrapper\n static values = {\n showEventName: String,\n hideEventName: String,\n }\n\n toggle() {\n const eventName = this.isWrapperHidden? this.showEventNameValue: this.hideEventNameValue\n if (this.isWrapperHidden) {\n this.showWrapper()\n }\n \n this.wrapperTarget.dispatchEvent(new CustomEvent(eventName))\n }\n \n get isWrapperHidden() {\n return this.wrapperTarget.classList.contains(this.hiddenClass)\n }\n \n showWrapper() {\n this.wrapperTarget.classList.remove(this.hiddenClass)\n }\n \n hideWrapper() {\n this.wrapperTarget.classList.add(this.hiddenClass)\n }\n}","import { identifierForContextKey } from \"@hotwired/stimulus-webpack-helpers\"\n\nimport ClipboardController from './clipboard_controller'\nimport FormController from './form_controller'\nimport MobileMenuController from './mobile_menu_controller'\n\nexport const controllerDefinitions = [\n [ClipboardController, 'clipboard_controller.js'],\n [FormController, 'form_controller.js'],\n [MobileMenuController, 'mobile_menu_controller.js'],\n].map(function(d) {\n const key = d[1]\n const controller = d[0]\n return {\n identifier: identifierForContextKey(key),\n controllerConstructor: controller\n }\n})\n","document.addEventListener(\"turbo:load\", () => {\n if (navigator.userAgent.toLocaleLowerCase().includes('electron')) {\n document.body.classList.add('electron')\n }\n})"],"names":["identifierForContextKey","key","logicalName","match","replace","Controller","copy","this","inputTarget","value","sourceTarget","innerText","select","document","execCommand","buttonTarget","innerHTML","setTimeout","getElementById","targets","removeTrailingNewlines","trixEditor","element","slice","removeTrailingWhitespace","resetOnSuccess","e","detail","success","target","reset","stripTrix","trixFieldTargets","forEach","editor","parentElement","querySelector","submitOnReturn","metaKey","ctrlKey","keyCode","preventDefault","form","closest","submitForm","requestSubmit","click","toggle","eventName","isWrapperHidden","showEventNameValue","hideEventNameValue","showWrapper","wrapperTarget","dispatchEvent","CustomEvent","classList","contains","hiddenClass","remove","hideWrapper","add","classes","values","showEventName","String","hideEventName","controllerDefinitions","ClipboardController","FormController","MobileMenuController","map","d","controller","identifier","controllerConstructor","addEventListener","navigator","userAgent","toLocaleLowerCase","includes","body"],"mappings":"gDAqBA,SAASA,EAAwBC,GAC7B,MAAMC,GAAeD,EAAIE,MAAM,2CAA6C,IAAI,GAChF,GAAID,EACA,OAAOA,EAAYE,QAAQ,KAAM,KAAKA,QAAQ,MAAO,sBCtBhCC,EAG3BC,OACEC,KAAKC,YAAYC,MAAQF,KAAKG,aAAaC,UAC3CJ,KAAKC,YAAYI,SACjBC,SAASC,YAAY,QACrBP,KAAKQ,aAAaC,UAAY,wEAC9BC,WAAW,WACTJ,SAASK,eAAe,UAAUF,UAAY,2DAC7C,SATEG,QAAU,CAAC,SAAU,QAAS,0BCAVd,yCA2B3Be,uBAA0BC,IACpBA,EAAWC,QAAQN,UAAUb,MAAM,kBACrCkB,EAAWC,QAAQN,UAAYK,EAAWC,QAAQN,UAAUO,MAAM,GAAI,IAAM,SAC5EhB,KAAKa,uBAAuBC,UAIhCG,yBAA4BH,IACtBA,EAAWC,QAAQN,UAAUb,MAAM,mBACrCkB,EAAWC,QAAQN,UAAYK,EAAWC,QAAQN,UAAUO,MAAM,GAAI,IAAM,SAC5EhB,KAAKiB,yBAAyBH,IACrBA,EAAWC,QAAQN,UAAUb,MAAM,qBAC5CkB,EAAWC,QAAQN,UAAYK,EAAWC,QAAQN,UAAUO,MAAM,GAAI,IAAM,SAC5EhB,KAAKiB,yBAAyBH,KArClCI,eAAeC,GACVA,EAAEC,OAAOC,SACVF,EAAEG,OAAOC,QAIbC,YACExB,KAAKyB,iBAAiBC,QAAQX,IAC5Bf,KAAKa,uBAAuBE,EAAQY,QACpC3B,KAAKiB,yBAAyBF,EAAQY,QAGtCZ,EAAQa,cAAcC,cAAc,SAAS3B,MAAQa,EAAQN,YAIjEqB,eAAeX,GACb,IAAIA,EAAEY,SAAWZ,EAAEa,UAA0B,IAAbb,EAAEc,QAAe,CAC/Cd,EAAEe,iBACF,IAAIC,EAAOhB,EAAEG,OAAOc,QAAQ,QAC5BpC,KAAKqC,WAAWF,IAqBpBE,WAAWF,GAGLA,EAAKG,cACPH,EAAKG,gBAELH,EAAKN,cAAc,iBAAiBU,WAjDjC3B,QAAU,CAAC,YAAa,0BCFJd,EAQ3B0C,SACE,MAAMC,EAAYzC,KAAK0C,gBAAiB1C,KAAK2C,mBAAoB3C,KAAK4C,mBAClE5C,KAAK0C,iBACP1C,KAAK6C,cAGP7C,KAAK8C,cAAcC,cAAc,IAAIC,YAAYP,IAG/CC,sBACF,YAAYI,cAAcG,UAAUC,SAASlD,KAAKmD,aAGpDN,cACE7C,KAAK8C,cAAcG,UAAUG,OAAOpD,KAAKmD,aAG3CE,cACErD,KAAK8C,cAAcG,UAAUK,IAAItD,KAAKmD,gBAzBjCvC,QAAU,CAAE,aACZ2C,QAAU,CAAE,YACZC,OAAS,CACdC,cAAeC,OACfC,cAAeD,QCDNE,MAAAA,EAAwB,CACnC,CAACC,EAAqB,2BACtB,CAACC,EAAgB,sBACjB,CAACC,EAAsB,8BACvBC,IAAI,SAASC,GACb,MACMC,EAAaD,EAAE,GACrB,MAAO,CACLE,WAAY1E,EAHFwE,EAAE,IAIZG,sBAAuBF,KCf3B5D,SAAS+D,iBAAiB,aAAc,KAClCC,UAAUC,UAAUC,oBAAoBC,SAAS,aACnDnE,SAASoE,KAAKzB,UAAUK,IAAI"}