bullet_train 1.0.76 → 1.0.83

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: 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"}