bullet_train 1.0.0 → 1.0.4

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.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/concerns/invitations/core.rb +46 -0
  3. data/app/models/concerns/memberships/core.rb +137 -0
  4. data/app/models/concerns/teams/core.rb +51 -0
  5. data/app/models/concerns/users/core.rb +164 -0
  6. data/app/models/invitation.rb +1 -52
  7. data/app/models/invitations.rb +5 -0
  8. data/app/models/membership.rb +1 -143
  9. data/app/models/memberships.rb +5 -0
  10. data/app/models/team.rb +1 -59
  11. data/app/models/teams.rb +5 -0
  12. data/app/models/user.rb +1 -170
  13. data/app/models/users.rb +5 -0
  14. data/config/routes.rb +40 -0
  15. data/lib/bullet_train/version.rb +1 -1
  16. metadata +9 -12
  17. data/app/controllers/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments_controller.rb +0 -83
  18. data/app/models/memberships/reassignments/assignment.rb +0 -12
  19. data/app/models/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignment.rb +0 -38
  20. data/app/models/memberships/reassignments.rb +0 -5
  21. data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_breadcrumbs.html.erb +0 -10
  22. data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_form.html.erb +0 -24
  23. data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_scaffolding_completely_concrete_tangible_things_reassignment.json.jbuilder +0 -8
  24. data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/index.json.jbuilder +0 -1
  25. data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/new.html.erb +0 -11
  26. data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/show.json.jbuilder +0 -1
  27. data/config/locales/en/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.en.yml +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcd7fed56a4e0b49d9ca96b95e4a804f6d8d48ef0443f45a28e9a9f28b811549
4
- data.tar.gz: 1bbc923e87403a718037831d9a7c6c7beeafb8192169b7897ce56ec0845c27dc
3
+ metadata.gz: 533e95a2c22629bce2db6569181981dd661621d7a2b17347d210f782554ac6f9
4
+ data.tar.gz: 7d179440547451b7ff69480478a8f2cb481e79b4b4ee3e80e1f950b219768150
5
5
  SHA512:
6
- metadata.gz: 53052501e61f60b496c5e82cd6f0c0cca4a1310a6093146c07f719556cd2005a260042de0dd05a289e0230e41e83d3b02195430134c1706d91a5c105bde35f09
7
- data.tar.gz: 9b34d71773c57d358ca7322ecdaf9948a676a42eca564245dc891f9d4a9bc2f94b731350e59d5d952aeedf7b45521bf2a153159c6ced6ccad8d1c1aff18cb97f
6
+ metadata.gz: '059cb145c72d2adf059902f5d0934d36362ad06ea6dc728a3951057b60758187e4f192e7bd24d62f0030fbd1c3d36e7797ed94ea3cd390b4c955a49fc4653301'
7
+ data.tar.gz: 108c4abc412f29b2321b1f6acc5c6c6e1e35d358fda6ba14f322dcfb49591c7ccc3c29ca10989e30ffcb915c5c2df727b9f4d4e044776f6e3a6f92d391f91389
@@ -0,0 +1,46 @@
1
+ module Invitations::Core
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ belongs_to :team
6
+ belongs_to :from_membership, class_name: "Membership"
7
+ has_one :membership, dependent: :nullify
8
+ has_many :roles, through: :membership
9
+
10
+ accepts_nested_attributes_for :membership
11
+
12
+ validates :email, presence: true
13
+
14
+ before_create :generate_uuid
15
+ after_create :set_added_by_membership
16
+ after_create :send_invitation_email
17
+ end
18
+
19
+ def set_added_by_membership
20
+ membership.update(added_by: from_membership)
21
+ end
22
+
23
+ def send_invitation_email
24
+ UserMailer.invited(uuid).deliver_later
25
+ end
26
+
27
+ def generate_uuid
28
+ self.uuid = SecureRandom.hex
29
+ end
30
+
31
+ def accept_for(user)
32
+ User.transaction do
33
+ user.memberships << membership
34
+ user.update(current_team: team, former_user: false)
35
+ destroy
36
+ end
37
+ end
38
+
39
+ def name
40
+ I18n.t("invitations.values.name", team_name: team.name)
41
+ end
42
+
43
+ def is_for?(user)
44
+ user.email.downcase.strip == email.downcase.strip
45
+ end
46
+ end
@@ -0,0 +1,137 @@
1
+ module Memberships::Core
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # See `docs/permissions.md` for details.
6
+ include Roles::Support
7
+
8
+ belongs_to :user, optional: true
9
+ belongs_to :team
10
+ belongs_to :invitation, optional: true, dependent: :destroy
11
+ belongs_to :added_by, class_name: "Membership", optional: true
12
+ belongs_to :platform_agent_of, class_name: "Platform::Application", optional: true
13
+
14
+ has_many :scaffolding_completely_concrete_tangible_things_assignments, class_name: "Scaffolding::CompletelyConcrete::TangibleThings::Assignment", dependent: :destroy
15
+ has_many :scaffolding_completely_concrete_tangible_things, through: :scaffolding_completely_concrete_tangible_things_assignments, source: :tangible_thing
16
+ has_many :reassignments_scaffolding_completely_concrete_tangible_things_reassignments, class_name: "Memberships::Reassignments::ScaffoldingCompletelyConcreteTangibleThingsReassignment", dependent: :destroy, foreign_key: :membership_id
17
+
18
+ has_many :scaffolding_absolutely_abstract_creative_concepts_collaborators, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcepts::Collaborator", dependent: :destroy
19
+
20
+ after_destroy do
21
+ # if we're destroying a user's membership to the team they have set as
22
+ # current, then we need to remove that so they don't get an error.
23
+ if user&.current_team == team
24
+ user.current_team = nil
25
+ user.save
26
+ end
27
+ end
28
+
29
+ scope :current_and_invited, -> { includes(:invitation).where("user_id IS NOT NULL OR invitations.id IS NOT NULL").references(:invitation) }
30
+ scope :current, -> { where("user_id IS NOT NULL") }
31
+ scope :tombstones, -> { includes(:invitation).where("user_id IS NULL AND invitations.id IS NULL").references(:invitation) }
32
+ end
33
+
34
+ def name
35
+ full_name
36
+ end
37
+
38
+ def label_string
39
+ full_name
40
+ end
41
+
42
+ # we overload this method so that when setting the list of role ids
43
+ # associated with a membership, admins can never remove the last admin
44
+ # of a team.
45
+ def role_ids=(ids)
46
+ # if this membership was an admin, and the new list of role ids don't include admin.
47
+ if admin? && !ids.include?(Role.admin.id)
48
+ unless team.admins.count > 1
49
+ raise RemovingLastTeamAdminException.new("You can't remove the last team admin.")
50
+ end
51
+ end
52
+
53
+ super(ids)
54
+ end
55
+
56
+ def unclaimed?
57
+ user.nil? && !invitation.nil?
58
+ end
59
+
60
+ def tombstone?
61
+ user.nil? && invitation.nil?
62
+ end
63
+
64
+ def last_admin?
65
+ return false unless admin?
66
+ return false unless user.present?
67
+ team.memberships.current.select(&:admin?) == [self]
68
+ end
69
+
70
+ def nullify_user
71
+ if last_admin?
72
+ raise RemovingLastTeamAdminException.new("You can't remove the last team admin.")
73
+ end
74
+
75
+ if (user_was = user)
76
+ unless user_first_name.present?
77
+ self.user_first_name = user.first_name
78
+ end
79
+
80
+ unless user_last_name.present?
81
+ self.user_last_name = user.last_name
82
+ end
83
+
84
+ unless user_profile_photo_id.present?
85
+ self.user_profile_photo_id = user.profile_photo_id
86
+ end
87
+
88
+ unless user_email.present?
89
+ self.user_email = user.email
90
+ end
91
+
92
+ self.user = nil
93
+ save
94
+
95
+ user_was.invalidate_ability_cache
96
+
97
+ user_was.update(
98
+ current_team: user_was.teams.first,
99
+ former_user: user_was.teams.empty?
100
+ )
101
+ end
102
+
103
+ # we do this here just in case by some weird chance an active membership had an invitation attached.
104
+ invitation&.destroy
105
+ end
106
+
107
+ def email
108
+ user&.email || user_email.presence || invitation&.email
109
+ end
110
+
111
+ def full_name
112
+ user&.full_name || [first_name.presence, last_name.presence].join(" ").presence || email
113
+ end
114
+
115
+ def first_name
116
+ user&.first_name || user_first_name
117
+ end
118
+
119
+ def last_name
120
+ user&.last_name || user_last_name
121
+ end
122
+
123
+ def last_initial
124
+ return nil unless last_name.present?
125
+ "#{last_name}."
126
+ end
127
+
128
+ def first_name_last_initial
129
+ [first_name, last_initial].map(&:present?).join(" ")
130
+ end
131
+
132
+ # TODO utilize this.
133
+ # members shouldn't receive notifications unless they are either an active user or an outstanding invitation.
134
+ def should_receive_notifications?
135
+ invitation.present? || user.present?
136
+ end
137
+ end
@@ -0,0 +1,51 @@
1
+ module Teams::Core
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # super scaffolding
6
+ unless scaffolding_things_disabled?
7
+ has_many :scaffolding_absolutely_abstract_creative_concepts, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcept", dependent: :destroy, enable_updates: true
8
+ end
9
+
10
+ # memberships and invitations
11
+ has_many :memberships, dependent: :destroy
12
+ has_many :users, through: :memberships
13
+ has_many :invitations
14
+
15
+ # oauth for grape api
16
+ has_many :platform_applications, class_name: "Platform::Application", dependent: :destroy, foreign_key: :team_id
17
+
18
+ # integrations
19
+ has_many :integrations_stripe_installations, class_name: "Integrations::StripeInstallation", dependent: :destroy if stripe_enabled?
20
+
21
+ # validations
22
+ validates :name, presence: true
23
+ validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
24
+ end
25
+
26
+ def admins
27
+ memberships.current_and_invited.admins
28
+ end
29
+
30
+ def admin_users
31
+ admins.map(&:user).compact
32
+ end
33
+
34
+ def primary_contact
35
+ admin_users.min { |user| user.created_at }
36
+ end
37
+
38
+ def formatted_email_address
39
+ primary_contact.email
40
+ end
41
+
42
+ def invalidate_caches
43
+ users.map(&:invalidate_ability_cache)
44
+ end
45
+
46
+ def team
47
+ # some generic features appeal to the `team` method for security or scoping purposes, but sometimes those same
48
+ # generic functions need to function for a team model as well, so we do this.
49
+ self
50
+ end
51
+ end
@@ -0,0 +1,164 @@
1
+ module Users::Core
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ if two_factor_authentication_enabled?
6
+ devise :two_factor_authenticatable, :two_factor_backupable, :omniauthable,
7
+ :registerable, :recoverable, :rememberable, :trackable, :validatable,
8
+ otp_secret_encryption_key: ENV["TWO_FACTOR_ENCRYPTION_KEY"]
9
+ else
10
+ devise :omniauthable, :database_authenticatable, :registerable,
11
+ :recoverable, :rememberable, :trackable, :validatable
12
+ end
13
+
14
+ # teams
15
+ has_many :memberships, dependent: :destroy
16
+ has_many :scaffolding_absolutely_abstract_creative_concepts_collaborators, through: :memberships
17
+ has_many :teams, through: :memberships
18
+ belongs_to :current_team, class_name: "Team", optional: true
19
+ accepts_nested_attributes_for :current_team
20
+
21
+ # oauth providers
22
+ has_many :oauth_stripe_accounts, class_name: "Oauth::StripeAccount" if stripe_enabled?
23
+
24
+ # platform functionality.
25
+ belongs_to :platform_agent_of, class_name: "Platform::Application", optional: true
26
+
27
+ # validations
28
+ validate :real_emails_only
29
+ validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
30
+
31
+ # callbacks
32
+ after_update :set_teams_time_zone
33
+ end
34
+
35
+ # TODO we need to update this to some sort of invalid email address or something
36
+ # people know to ignore. it would be a security problem to have this pointing
37
+ # at anybody's real email address.
38
+ def email_is_oauth_placeholder?
39
+ !!email.match(/noreply\+.*@bullettrain.co/)
40
+ end
41
+
42
+ def label_string
43
+ name
44
+ end
45
+
46
+ def name
47
+ full_name.present? ? full_name : email
48
+ end
49
+
50
+ def full_name
51
+ [first_name_was, last_name_was].select(&:present?).join(" ")
52
+ end
53
+
54
+ def details_provided?
55
+ first_name.present? && last_name.present? && current_team.name.present?
56
+ end
57
+
58
+ def send_welcome_email
59
+ UserMailer.welcome(self).deliver_later
60
+ end
61
+
62
+ def create_default_team
63
+ # This creates a `Membership`, because `User` `has_many :teams, through: :memberships`
64
+ # TODO The team name should take into account the user's current locale.
65
+ default_team = teams.create(name: "Your Team", time_zone: time_zone)
66
+ memberships.find_by(team: default_team).update role_ids: [Role.admin.id]
67
+ update(current_team: default_team)
68
+ end
69
+
70
+ def real_emails_only
71
+ if ENV["REALEMAIL_API_KEY"] && !Rails.env.test?
72
+ uri = URI("https://realemail.expeditedaddons.com")
73
+
74
+ # Change the input parameters here
75
+ uri.query = URI.encode_www_form({
76
+ api_key: ENV["REAL_EMAIL_KEY"],
77
+ email: email,
78
+ fix_typos: false
79
+ })
80
+
81
+ # Results are returned as a JSON object
82
+ result = JSON.parse(Net::HTTP.get_response(uri).body)
83
+
84
+ if result["syntax_error"]
85
+ errors.add(:email, "is not a valid email address")
86
+ elsif result["domain_error"] || (result.key?("mx_records_found") && !result["mx_records_found"])
87
+ errors.add(:email, "can't actually receive emails")
88
+ elsif result["is_disposable"]
89
+ errors.add(:email, "is a disposable email address")
90
+ end
91
+ end
92
+ end
93
+
94
+ def multiple_teams?
95
+ teams.count > 1
96
+ end
97
+
98
+ def one_team?
99
+ !multiple_teams?
100
+ end
101
+
102
+ def formatted_email_address
103
+ if details_provided?
104
+ "\"#{first_name} #{last_name}\" <#{email}>"
105
+ else
106
+ email
107
+ end
108
+ end
109
+
110
+ def administrating_team_ids
111
+ parent_ids_for(Role.admin, :memberships, :team)
112
+ end
113
+
114
+ def parent_ids_for(role, through, parent)
115
+ parent_id_column = "#{parent}_id"
116
+ key = "#{role.key}_#{through}_#{parent_id_column}s"
117
+ return ability_cache[key] if ability_cache && ability_cache[key]
118
+ role = nil if role.default?
119
+ value = send(through).with_role(role).distinct.pluck(parent_id_column)
120
+ current_cache = ability_cache || {}
121
+ current_cache[key] = value
122
+ update_column :ability_cache, current_cache
123
+ value
124
+ end
125
+
126
+ def invalidate_ability_cache
127
+ update_column(:ability_cache, {})
128
+ end
129
+
130
+ def otp_qr_code
131
+ issuer = I18n.t("application.name")
132
+ label = "#{issuer}:#{email}"
133
+ RQRCode::QRCode.new(otp_provisioning_uri(label, issuer: issuer))
134
+ end
135
+
136
+ def scaffolding_absolutely_abstract_creative_concepts_collaborators
137
+ Scaffolding::AbsolutelyAbstract::CreativeConcepts::Collaborator.joins(:membership).where(membership: {user_id: id})
138
+ end
139
+
140
+ def admin_scaffolding_absolutely_abstract_creative_concepts_ids
141
+ scaffolding_absolutely_abstract_creative_concepts_collaborators.admins.pluck(:creative_concept_id)
142
+ end
143
+
144
+ def editor_scaffolding_absolutely_abstract_creative_concepts_ids
145
+ scaffolding_absolutely_abstract_creative_concepts_collaborators.editors.pluck(:creative_concept_id)
146
+ end
147
+
148
+ def viewer_scaffolding_absolutely_abstract_creative_concepts_ids
149
+ scaffolding_absolutely_abstract_creative_concepts_collaborators.viewers.pluck(:creative_concept_id)
150
+ end
151
+
152
+ def developer?
153
+ return false unless ENV["DEVELOPER_EMAILS"]
154
+ # we use email_was so they can't try setting their email to the email of an admin.
155
+ return false unless email_was
156
+ ENV["DEVELOPER_EMAILS"].split(",").include?(email_was)
157
+ end
158
+
159
+ def set_teams_time_zone
160
+ teams.where(time_zone: nil).each do |team|
161
+ team.update(time_zone: time_zone) if team.users.count == 1
162
+ end
163
+ end
164
+ end
@@ -1,25 +1,5 @@
1
1
  class Invitation < ApplicationRecord
2
- # 🚫 DEFAULT BULLET TRAIN INVITATION FUNCTIONALITY
3
- # Typically you should avoid adding your own functionality in this section to avoid merge conflicts in the future.
4
- # (If you specifically want to change Bullet Train's default behavior, that's OK and you can do that here.)
5
-
6
- belongs_to :team
7
- belongs_to :from_membership, class_name: "Membership"
8
- has_one :membership, dependent: :nullify
9
- has_many :roles, through: :membership
10
-
11
- accepts_nested_attributes_for :membership
12
-
13
- validates :email, presence: true
14
-
15
- before_create :generate_uuid
16
- after_create :set_added_by_membership
17
- after_create :send_invitation_email
18
-
19
- # ✅ YOUR APPLICATION'S INVITATION FUNCTIONALITY
20
- # This is the place where you should implement your own features on top of Bullet Train's functionality. There
21
- # are a bunch of Super Scaffolding hooks here by default to try and help keep generated code logically organized.
22
-
2
+ include Invitations::Core
23
3
  # 🚅 add concerns above.
24
4
 
25
5
  # 🚅 add belongs_to associations above.
@@ -39,35 +19,4 @@ class Invitation < ApplicationRecord
39
19
  # 🚅 add delegations above.
40
20
 
41
21
  # 🚅 add methods above.
42
-
43
- # 🚫 DEFAULT BULLET TRAIN INVITATION FUNCTIONALITY
44
- # We put these at the bottom of this file to keep them out of the way. You should define your own methods above here.
45
-
46
- def set_added_by_membership
47
- membership.update(added_by: from_membership)
48
- end
49
-
50
- def send_invitation_email
51
- UserMailer.invited(uuid).deliver_later
52
- end
53
-
54
- def generate_uuid
55
- self.uuid = SecureRandom.hex
56
- end
57
-
58
- def accept_for(user)
59
- User.transaction do
60
- user.memberships << membership
61
- user.update(current_team: team, former_user: false)
62
- destroy
63
- end
64
- end
65
-
66
- def name
67
- I18n.t("invitations.values.name", team_name: team.name)
68
- end
69
-
70
- def is_for?(user)
71
- user.email.downcase.strip == email.downcase.strip
72
- end
73
22
  end
@@ -0,0 +1,5 @@
1
+ module Invitations
2
+ def self.table_name_prefix
3
+ "users_"
4
+ end
5
+ end
@@ -1,40 +1,5 @@
1
1
  class Membership < ApplicationRecord
2
- # 🚫 DEFAULT BULLET TRAIN MEMBERSHIP FUNCTIONALITY
3
- # Typically you should avoid adding your own functionality in this section to avoid merge conflicts in the future.
4
- # (If you specifically want to change Bullet Train's default behavior, that's OK and you can do that here.)
5
-
6
- # See `docs/permissions.md` for details.
7
- include Roles::Support
8
-
9
- belongs_to :user, optional: true
10
- belongs_to :team
11
- belongs_to :invitation, optional: true, dependent: :destroy
12
- belongs_to :added_by, class_name: "Membership", optional: true
13
- belongs_to :platform_agent_of, class_name: "Platform::Application", optional: true
14
-
15
- has_many :scaffolding_completely_concrete_tangible_things_assignments, class_name: "Scaffolding::CompletelyConcrete::TangibleThings::Assignment", dependent: :destroy
16
- has_many :scaffolding_completely_concrete_tangible_things, through: :scaffolding_completely_concrete_tangible_things_assignments, source: :tangible_thing
17
- has_many :reassignments_scaffolding_completely_concrete_tangible_things_reassignments, class_name: "Memberships::Reassignments::ScaffoldingCompletelyConcreteTangibleThingsReassignment", dependent: :destroy, foreign_key: :membership_id
18
-
19
- has_many :scaffolding_absolutely_abstract_creative_concepts_collaborators, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcepts::Collaborator", dependent: :destroy
20
-
21
- after_destroy do
22
- # if we're destroying a user's membership to the team they have set as
23
- # current, then we need to remove that so they don't get an error.
24
- if user&.current_team == team
25
- user.current_team = nil
26
- user.save
27
- end
28
- end
29
-
30
- scope :current_and_invited, -> { includes(:invitation).where("user_id IS NOT NULL OR invitations.id IS NOT NULL").references(:invitation) }
31
- scope :current, -> { where("user_id IS NOT NULL") }
32
- scope :tombstones, -> { includes(:invitation).where("user_id IS NULL AND invitations.id IS NULL").references(:invitation) }
33
-
34
- # ✅ YOUR APPLICATION'S MEMBERSHIP FUNCTIONALITY
35
- # This is the place where you should implement your own features on top of Bullet Train's functionality. There
36
- # are a bunch of Super Scaffolding hooks here by default to try and help keep generated code logically organized.
37
-
2
+ include Memberships::Core
38
3
  # 🚅 add concerns above.
39
4
 
40
5
  # 🚅 add belongs_to associations above.
@@ -54,111 +19,4 @@ class Membership < ApplicationRecord
54
19
  # 🚅 add delegations above.
55
20
 
56
21
  # 🚅 add methods above.
57
-
58
- # 🚫 DEFAULT BULLET TRAIN TEAM FUNCTIONALITY
59
- # We put these at the bottom of this file to keep them out of the way. You should define your own methods above here.
60
-
61
- def name
62
- full_name
63
- end
64
-
65
- def label_string
66
- full_name
67
- end
68
-
69
- # we overload this method so that when setting the list of role ids
70
- # associated with a membership, admins can never remove the last admin
71
- # of a team.
72
- def role_ids=(ids)
73
- # if this membership was an admin, and the new list of role ids don't include admin.
74
- if admin? && !ids.include?(Role.admin.id)
75
- unless team.admins.count > 1
76
- raise RemovingLastTeamAdminException.new("You can't remove the last team admin.")
77
- end
78
- end
79
-
80
- super(ids)
81
- end
82
-
83
- def unclaimed?
84
- user.nil? && !invitation.nil?
85
- end
86
-
87
- def tombstone?
88
- user.nil? && invitation.nil?
89
- end
90
-
91
- def last_admin?
92
- return false unless admin?
93
- return false unless user.present?
94
- team.memberships.current.select(&:admin?) == [self]
95
- end
96
-
97
- def nullify_user
98
- if last_admin?
99
- raise RemovingLastTeamAdminException.new("You can't remove the last team admin.")
100
- end
101
-
102
- if (user_was = user)
103
- unless user_first_name.present?
104
- self.user_first_name = user.first_name
105
- end
106
-
107
- unless user_last_name.present?
108
- self.user_last_name = user.last_name
109
- end
110
-
111
- unless user_profile_photo_id.present?
112
- self.user_profile_photo_id = user.profile_photo_id
113
- end
114
-
115
- unless user_email.present?
116
- self.user_email = user.email
117
- end
118
-
119
- self.user = nil
120
- save
121
-
122
- user_was.invalidate_ability_cache
123
-
124
- user_was.update(
125
- current_team: user_was.teams.first,
126
- former_user: user_was.teams.empty?
127
- )
128
- end
129
-
130
- # we do this here just in case by some weird chance an active membership had an invitation attached.
131
- invitation&.destroy
132
- end
133
-
134
- def email
135
- user&.email || user_email.presence || invitation&.email
136
- end
137
-
138
- def full_name
139
- user&.full_name || [first_name.presence, last_name.presence].join(" ").presence || email
140
- end
141
-
142
- def first_name
143
- user&.first_name || user_first_name
144
- end
145
-
146
- def last_name
147
- user&.last_name || user_last_name
148
- end
149
-
150
- def last_initial
151
- return nil unless last_name.present?
152
- "#{last_name}."
153
- end
154
-
155
- def first_name_last_initial
156
- [first_name, last_initial].map(&:present?).join(" ")
157
- end
158
-
159
- # TODO utilize this.
160
- # members shouldn't receive notifications unless they are either an active user or an outstanding invitation.
161
- def should_receive_notifications?
162
- invitation.present? || user.present?
163
- end
164
22
  end
@@ -0,0 +1,5 @@
1
+ module Memberships
2
+ def self.table_name_prefix
3
+ "memberships_"
4
+ end
5
+ end
data/app/models/team.rb CHANGED
@@ -1,35 +1,6 @@
1
1
  class Team < ApplicationRecord
2
- # 🚫 DEFAULT BULLET TRAIN TEAM FUNCTIONALITY
3
- # Typically you should avoid adding your own functionality in this section to avoid merge conflicts in the future.
4
- # (If you specifically want to change Bullet Train's default behavior, that's OK and you can do that here.)
5
-
6
- # Outgoing webhooks.
2
+ include Teams::Core
7
3
  include Webhooks::Outgoing::TeamSupport
8
-
9
- # super scaffolding
10
- unless scaffolding_things_disabled?
11
- has_many :scaffolding_absolutely_abstract_creative_concepts, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcept", dependent: :destroy, enable_updates: true
12
- end
13
-
14
- # memberships and invitations
15
- has_many :memberships, dependent: :destroy
16
- has_many :users, through: :memberships
17
- has_many :invitations
18
-
19
- # oauth for grape api
20
- has_many :platform_applications, class_name: "Platform::Application", dependent: :destroy, foreign_key: :team_id
21
-
22
- # integrations
23
- has_many :integrations_stripe_installations, class_name: "Integrations::StripeInstallation", dependent: :destroy if stripe_enabled?
24
-
25
- # validations
26
- validates :name, presence: true
27
- validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
28
-
29
- # ✅ YOUR APPLICATION'S TEAM FUNCTIONALITY
30
- # This is the place where you should implement your own features on top of Bullet Train's functionality. There
31
- # are a bunch of Super Scaffolding hooks here by default to try and help keep generated code logically organized.
32
-
33
4
  # 🚅 add concerns above.
34
5
 
35
6
  # 🚅 add belongs_to associations above.
@@ -49,33 +20,4 @@ class Team < ApplicationRecord
49
20
  # 🚅 add delegations above.
50
21
 
51
22
  # 🚅 add methods above.
52
-
53
- # 🚫 DEFAULT BULLET TRAIN TEAM FUNCTIONALITY
54
- # We put these at the bottom of this file to keep them out of the way. You should define your own methods above here.
55
-
56
- def admins
57
- memberships.current_and_invited.admins
58
- end
59
-
60
- def admin_users
61
- admins.map(&:user).compact
62
- end
63
-
64
- def primary_contact
65
- admin_users.min { |user| user.created_at }
66
- end
67
-
68
- def formatted_email_address
69
- primary_contact.email
70
- end
71
-
72
- def invalidate_caches
73
- users.map(&:invalidate_ability_cache)
74
- end
75
-
76
- def team
77
- # some generic features appeal to the `team` method for security or scoping purposes, but sometimes those same
78
- # generic functions need to function for a team model as well, so we do this.
79
- self
80
- end
81
23
  end
@@ -0,0 +1,5 @@
1
+ module Teams
2
+ def self.table_name_prefix
3
+ "teams_"
4
+ end
5
+ end
data/app/models/user.rb CHANGED
@@ -1,41 +1,5 @@
1
1
  class User < ApplicationRecord
2
- # 🚫 DEFAULT BULLET TRAIN USER FUNCTIONALITY
3
- # Typically you should avoid adding your own functionality in this section to avoid merge conflicts in the future.
4
- # (If you specifically want to change Bullet Train's default behavior, that's OK and you can do that here.)
5
-
6
- if two_factor_authentication_enabled?
7
- devise :two_factor_authenticatable, :two_factor_backupable, :omniauthable,
8
- :registerable, :recoverable, :rememberable, :trackable, :validatable,
9
- otp_secret_encryption_key: ENV["TWO_FACTOR_ENCRYPTION_KEY"]
10
- else
11
- devise :omniauthable, :database_authenticatable, :registerable,
12
- :recoverable, :rememberable, :trackable, :validatable
13
- end
14
-
15
- # teams
16
- has_many :memberships, dependent: :destroy
17
- has_many :scaffolding_absolutely_abstract_creative_concepts_collaborators, through: :memberships
18
- has_many :teams, through: :memberships
19
- belongs_to :current_team, class_name: "Team", optional: true
20
- accepts_nested_attributes_for :current_team
21
-
22
- # oauth providers
23
- has_many :oauth_stripe_accounts, class_name: "Oauth::StripeAccount" if stripe_enabled?
24
-
25
- # platform functionality.
26
- belongs_to :platform_agent_of, class_name: "Platform::Application", optional: true
27
-
28
- # validations
29
- validate :real_emails_only
30
- validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
31
-
32
- # callbacks
33
- after_update :set_teams_time_zone
34
-
35
- # ✅ YOUR APPLICATION'S USER FUNCTIONALITY
36
- # This is the place where you should implement your own features on top of Bullet Train's user functionality. There
37
- # are a bunch of Super Scaffolding hooks here by default to try and help keep generated code logically organized.
38
-
2
+ include Users::Core
39
3
  # 🚅 add concerns above.
40
4
 
41
5
  # 🚅 add belongs_to associations above.
@@ -55,137 +19,4 @@ class User < ApplicationRecord
55
19
  # 🚅 add delegations above.
56
20
 
57
21
  # 🚅 add methods above.
58
-
59
- # 🚫 DEFAULT BULLET TRAIN USER FUNCTIONALITY
60
- # We put these at the bottom of this file to keep them out of the way. You should define your own methods above here.
61
-
62
- # TODO we need to update this to some sort of invalid email address or something
63
- # people know to ignore. it would be a security problem to have this pointing
64
- # at anybody's real email address.
65
- def email_is_oauth_placeholder?
66
- !!email.match(/noreply\+.*@bullettrain.co/)
67
- end
68
-
69
- def label_string
70
- name
71
- end
72
-
73
- def name
74
- full_name.present? ? full_name : email
75
- end
76
-
77
- def full_name
78
- [first_name_was, last_name_was].select(&:present?).join(" ")
79
- end
80
-
81
- def details_provided?
82
- first_name.present? && last_name.present? && current_team.name.present?
83
- end
84
-
85
- def send_welcome_email
86
- UserMailer.welcome(self).deliver_later
87
- end
88
-
89
- def create_default_team
90
- # This creates a `Membership`, because `User` `has_many :teams, through: :memberships`
91
- # TODO The team name should take into account the user's current locale.
92
- default_team = teams.create(name: "Your Team", time_zone: time_zone)
93
- memberships.find_by(team: default_team).update role_ids: [Role.admin.id]
94
- update(current_team: default_team)
95
- end
96
-
97
- def real_emails_only
98
- if ENV["REALEMAIL_API_KEY"] && !Rails.env.test?
99
- uri = URI("https://realemail.expeditedaddons.com")
100
-
101
- # Change the input parameters here
102
- uri.query = URI.encode_www_form({
103
- api_key: ENV["REAL_EMAIL_KEY"],
104
- email: email,
105
- fix_typos: false
106
- })
107
-
108
- # Results are returned as a JSON object
109
- result = JSON.parse(Net::HTTP.get_response(uri).body)
110
-
111
- if result["syntax_error"]
112
- errors.add(:email, "is not a valid email address")
113
- elsif result["domain_error"] || (result.key?("mx_records_found") && !result["mx_records_found"])
114
- errors.add(:email, "can't actually receive emails")
115
- elsif result["is_disposable"]
116
- errors.add(:email, "is a disposable email address")
117
- end
118
- end
119
- end
120
-
121
- def multiple_teams?
122
- teams.count > 1
123
- end
124
-
125
- def one_team?
126
- !multiple_teams?
127
- end
128
-
129
- def formatted_email_address
130
- if details_provided?
131
- "\"#{first_name} #{last_name}\" <#{email}>"
132
- else
133
- email
134
- end
135
- end
136
-
137
- def administrating_team_ids
138
- parent_ids_for(Role.admin, :memberships, :team)
139
- end
140
-
141
- def parent_ids_for(role, through, parent)
142
- parent_id_column = "#{parent}_id"
143
- key = "#{role.key}_#{through}_#{parent_id_column}s"
144
- return ability_cache[key] if ability_cache && ability_cache[key]
145
- role = nil if role.default?
146
- value = send(through).with_role(role).distinct.pluck(parent_id_column)
147
- current_cache = ability_cache || {}
148
- current_cache[key] = value
149
- update_column :ability_cache, current_cache
150
- value
151
- end
152
-
153
- def invalidate_ability_cache
154
- update_column(:ability_cache, {})
155
- end
156
-
157
- def otp_qr_code
158
- issuer = I18n.t("application.name")
159
- label = "#{issuer}:#{email}"
160
- RQRCode::QRCode.new(otp_provisioning_uri(label, issuer: issuer))
161
- end
162
-
163
- def scaffolding_absolutely_abstract_creative_concepts_collaborators
164
- Scaffolding::AbsolutelyAbstract::CreativeConcepts::Collaborator.joins(:membership).where(membership: {user_id: id})
165
- end
166
-
167
- def admin_scaffolding_absolutely_abstract_creative_concepts_ids
168
- scaffolding_absolutely_abstract_creative_concepts_collaborators.admins.pluck(:creative_concept_id)
169
- end
170
-
171
- def editor_scaffolding_absolutely_abstract_creative_concepts_ids
172
- scaffolding_absolutely_abstract_creative_concepts_collaborators.editors.pluck(:creative_concept_id)
173
- end
174
-
175
- def viewer_scaffolding_absolutely_abstract_creative_concepts_ids
176
- scaffolding_absolutely_abstract_creative_concepts_collaborators.viewers.pluck(:creative_concept_id)
177
- end
178
-
179
- def developer?
180
- return false unless ENV["DEVELOPER_EMAILS"]
181
- # we use email_was so they can't try setting their email to the email of an admin.
182
- return false unless email_was
183
- ENV["DEVELOPER_EMAILS"].split(",").include?(email_was)
184
- end
185
-
186
- def set_teams_time_zone
187
- teams.where(time_zone: nil).each do |team|
188
- team.update(time_zone: time_zone) if team.users.count == 1
189
- end
190
- end
191
22
  end
@@ -0,0 +1,5 @@
1
+ module Users
2
+ def self.table_name_prefix
3
+ "users_"
4
+ end
5
+ end
data/config/routes.rb CHANGED
@@ -1,2 +1,42 @@
1
1
  Rails.application.routes.draw do
2
+ namespace :account do
3
+ shallow do
4
+ resource :two_factor, only: [:create, :destroy]
5
+
6
+ # user-level onboarding tasks.
7
+ namespace :onboarding do
8
+ resources :user_details
9
+ resources :user_email
10
+ end
11
+
12
+ # user specific resources.
13
+ resources :users
14
+
15
+ # team-level resources.
16
+ resources :teams do
17
+ resources :invitations do
18
+ member do
19
+ get :accept
20
+ post :accept
21
+ end
22
+ end
23
+
24
+ resources :memberships do
25
+ member do
26
+ post :demote
27
+ post :promote
28
+ post :reinvite
29
+ end
30
+
31
+ collection do
32
+ get :search
33
+ end
34
+ end
35
+
36
+ member do
37
+ post :switch_to
38
+ end
39
+ end
40
+ end
41
+ end
2
42
  end
@@ -1,3 +1,3 @@
1
1
  module BulletTrain
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet_train
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Culver
@@ -36,7 +36,6 @@ files:
36
36
  - Rakefile
37
37
  - app/assets/config/bullet_train_manifest.js
38
38
  - app/controllers/account/invitations_controller.rb
39
- - app/controllers/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments_controller.rb
40
39
  - app/controllers/account/memberships_controller.rb
41
40
  - app/controllers/account/onboarding/user_details_controller.rb
42
41
  - app/controllers/account/onboarding/user_email_controller.rb
@@ -48,13 +47,18 @@ files:
48
47
  - app/helpers/account/users_helper.rb
49
48
  - app/helpers/invitation_only_helper.rb
50
49
  - app/helpers/invitations_helper.rb
50
+ - app/models/concerns/invitations/core.rb
51
+ - app/models/concerns/memberships/core.rb
52
+ - app/models/concerns/teams/core.rb
53
+ - app/models/concerns/users/core.rb
51
54
  - app/models/invitation.rb
55
+ - app/models/invitations.rb
52
56
  - app/models/membership.rb
53
- - app/models/memberships/reassignments.rb
54
- - app/models/memberships/reassignments/assignment.rb
55
- - app/models/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignment.rb
57
+ - app/models/memberships.rb
56
58
  - app/models/team.rb
59
+ - app/models/teams.rb
57
60
  - app/models/user.rb
61
+ - app/models/users.rb
58
62
  - app/views/account/invitations/_breadcrumbs.html.erb
59
63
  - app/views/account/invitations/_form.html.erb
60
64
  - app/views/account/invitations/_invitation.json.jbuilder
@@ -69,12 +73,6 @@ files:
69
73
  - app/views/account/memberships/_tombstones.html.erb
70
74
  - app/views/account/memberships/edit.html.erb
71
75
  - app/views/account/memberships/index.html.erb
72
- - app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_breadcrumbs.html.erb
73
- - app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_form.html.erb
74
- - app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_scaffolding_completely_concrete_tangible_things_reassignment.json.jbuilder
75
- - app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/index.json.jbuilder
76
- - app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/new.html.erb
77
- - app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/show.json.jbuilder
78
76
  - app/views/account/memberships/show.html.erb
79
77
  - app/views/account/onboarding/user_details/edit.html.erb
80
78
  - app/views/account/onboarding/user_email/edit.html.erb
@@ -95,7 +93,6 @@ files:
95
93
  - app/views/account/users/show.html.erb
96
94
  - config/locales/en/invitations.en.yml
97
95
  - config/locales/en/memberships.en.yml
98
- - config/locales/en/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.en.yml
99
96
  - config/locales/en/onboarding/user_details.en.yml
100
97
  - config/locales/en/onboarding/user_email.en.yml
101
98
  - config/locales/en/teams.en.yml
@@ -1,83 +0,0 @@
1
- class Account::Memberships::Reassignments::ScaffoldingCompletelyConcreteTangibleThingsReassignmentsController < Account::ApplicationController
2
- account_load_and_authorize_resource :scaffolding_completely_concrete_tangible_things_reassignment, through: :membership, through_association: :reassignments_scaffolding_completely_concrete_tangible_things_reassignments
3
-
4
- # GET /account/memberships/:membership_id/reassignments/scaffolding_completely_concrete_tangible_things_reassignments
5
- # GET /account/memberships/:membership_id/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.json
6
- def index
7
- # if you only want these objects shown on their parent's show page, uncomment this:
8
- redirect_to [:account, @membership]
9
- end
10
-
11
- # GET /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id
12
- # GET /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id.json
13
- def show
14
- end
15
-
16
- # GET /account/memberships/:membership_id/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/new
17
- def new
18
- end
19
-
20
- # GET /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id/edit
21
- def edit
22
- end
23
-
24
- # POST /account/memberships/:membership_id/reassignments/scaffolding_completely_concrete_tangible_things_reassignments
25
- # POST /account/memberships/:membership_id/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.json
26
- def create
27
- respond_to do |format|
28
- if @scaffolding_completely_concrete_tangible_things_reassignment.save
29
- format.html { redirect_to [:account, @membership, :reassignments_scaffolding_completely_concrete_tangible_things_reassignments], notice: I18n.t("memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.notifications.created") }
30
- format.json { render :show, status: :created, location: [:account, @scaffolding_completely_concrete_tangible_things_reassignment] }
31
- else
32
- format.html { render :new, status: :unprocessable_entity }
33
- format.json { render json: @scaffolding_completely_concrete_tangible_things_reassignment.errors, status: :unprocessable_entity }
34
- end
35
- end
36
- end
37
-
38
- # PATCH/PUT /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id
39
- # PATCH/PUT /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id.json
40
- def update
41
- respond_to do |format|
42
- if @scaffolding_completely_concrete_tangible_things_reassignment.update(scaffolding_completely_concrete_tangible_things_reassignment_params)
43
- format.html { redirect_to [:account, @scaffolding_completely_concrete_tangible_things_reassignment], notice: I18n.t("memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.notifications.updated") }
44
- format.json { render :show, status: :ok, location: [:account, @scaffolding_completely_concrete_tangible_things_reassignment] }
45
- else
46
- format.html { render :edit, status: :unprocessable_entity }
47
- format.json { render json: @scaffolding_completely_concrete_tangible_things_reassignment.errors, status: :unprocessable_entity }
48
- end
49
- end
50
- end
51
-
52
- # DELETE /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id
53
- # DELETE /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id.json
54
- def destroy
55
- @scaffolding_completely_concrete_tangible_things_reassignment.destroy
56
- respond_to do |format|
57
- format.html { redirect_to [:account, @membership, :reassignments_scaffolding_completely_concrete_tangible_things_reassignments], notice: I18n.t("memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.notifications.destroyed") }
58
- format.json { head :no_content }
59
- end
60
- end
61
-
62
- private
63
-
64
- # Never trust parameters from the scary internet, only allow the white list through.
65
- def scaffolding_completely_concrete_tangible_things_reassignment_params
66
- strong_params = params.require(:memberships_reassignments_scaffolding_completely_concrete_tangible_things_reassignment).permit(
67
- # 🚅 super scaffolding will insert new fields above this line.
68
- # 🚅 super scaffolding will insert new arrays above this line.
69
- membership_ids: [],
70
- )
71
-
72
- strong_params[:membership_ids] = create_models_if_new(strong_params[:membership_ids]) do |email|
73
- # stub out a new membership and invitation with no special permissions.
74
- membership = current_team.memberships.create(user_email: email, added_by: current_membership)
75
- current_team.invitations.create(email: email, from_membership: current_membership, membership: membership)
76
- membership
77
- end
78
-
79
- # 🚅 super scaffolding will insert processing for new fields above this line.
80
-
81
- strong_params
82
- end
83
- end
@@ -1,12 +0,0 @@
1
- class Memberships::Reassignments::Assignment < ApplicationRecord
2
- belongs_to :scaffolding_completely_concrete_tangible_things_reassignment, class_name: "Memberships::Reassignments::ScaffoldingCompletelyConcreteTangibleThingsReassignment"
3
- belongs_to :membership
4
-
5
- validate :validate_membership
6
-
7
- def validate_membership
8
- unless scaffolding_completely_concrete_tangible_things_reassignment.valid_memberships.include?(membership)
9
- errors.add(:membership, :invalid)
10
- end
11
- end
12
- end
@@ -1,38 +0,0 @@
1
- class Memberships::Reassignments::ScaffoldingCompletelyConcreteTangibleThingsReassignment < ApplicationRecord
2
- # 🚅 add concerns above.
3
-
4
- belongs_to :membership # this is the member being reassigned from.
5
- has_one :team, through: :membership
6
- # 🚅 add belongs_to associations above.
7
-
8
- has_many :assignments
9
- has_many :memberships, through: :assignments # these are the members being reassigned to.
10
- # 🚅 add has_many associations above.
11
-
12
- # 🚅 add has_one associations above.
13
-
14
- # 🚅 add scopes above.
15
-
16
- # 🚅 add validations above.
17
-
18
- after_save :reassign
19
- # 🚅 add callbacks above.
20
-
21
- # 🚅 add delegations above.
22
-
23
- def valid_memberships
24
- team.memberships.current_and_invited
25
- end
26
-
27
- def reassign
28
- membership.scaffolding_completely_concrete_tangible_things_assignments.each do |existing_assignment|
29
- memberships.each do |target_membership|
30
- unless existing_assignment.tangible_thing.memberships.include?(target_membership)
31
- existing_assignment.tangible_thing.memberships << target_membership
32
- end
33
- end
34
- existing_assignment.destroy
35
- end
36
- end
37
- # 🚅 add methods above.
38
- end
@@ -1,5 +0,0 @@
1
- module Memberships::Reassignments
2
- def self.table_name_prefix
3
- "memberships_reassignments_"
4
- end
5
- end
@@ -1,10 +0,0 @@
1
- <% scaffolding_completely_concrete_tangible_things_reassignment ||= @scaffolding_completely_concrete_tangible_things_reassignment %>
2
- <% membership ||= @membership || scaffolding_completely_concrete_tangible_things_reassignment&.membership %>
3
- <%= render 'account/memberships/breadcrumbs', membership: membership %>
4
- <li class="breadcrumb-item">
5
- <%= link_to t('.label'), [:account, membership, :reassignments_scaffolding_completely_concrete_tangible_things_reassignments] %>
6
- </li>
7
- <% if scaffolding_completely_concrete_tangible_things_reassignment&.persisted? %>
8
- <li class="breadcrumb-item"><%= scaffolding_completely_concrete_tangible_things_reassignment.label_string %></li>
9
- <% end %>
10
- <%= render 'account/shared/breadcrumbs/actions', only_for: 'memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments' %>
@@ -1,24 +0,0 @@
1
- <%= form_with(model: scaffolding_completely_concrete_tangible_things_reassignment, url: (scaffolding_completely_concrete_tangible_things_reassignment.persisted? ? [:account, scaffolding_completely_concrete_tangible_things_reassignment] : [:account, @membership, :reassignments_scaffolding_completely_concrete_tangible_things_reassignments]), local: true) do |form| %>
2
- <%= render 'account/shared/forms/errors', form: form %>
3
-
4
- <%= render 'shared/fields/super_select', form: form, method: :membership_ids, options: {autofocus: true},
5
- html_options: {multiple: true, class: can_invite? ? 'accepts-new' : ''},
6
- choices: memberships_as_select_options(scaffolding_completely_concrete_tangible_things_reassignment.valid_memberships, form.object.membership_ids) do %>
7
- <% if can_invite? %>
8
- <% content_for :after_help do %>
9
- <%= t('memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.fields.membership_ids.invite_help').html_safe %>
10
- <% end %>
11
- <% end %>
12
- <% end %>
13
- <%# 🚅 super scaffolding will insert new fields above this line. %>
14
-
15
- <div class="form-buttons-w">
16
- <%= form.submit (form.object.persisted? ? t('.buttons.update') : t('.buttons.create')), class: "btn btn-primary" %>
17
- <% if form.object.persisted? %>
18
- <%= link_to t('global.buttons.cancel'), [:account, scaffolding_completely_concrete_tangible_things_reassignment], class: "btn btn-link" %>
19
- <% else %>
20
- <%= link_to t('global.buttons.cancel'), [:account, @membership, :reassignments_scaffolding_completely_concrete_tangible_things_reassignments], class: "btn btn-link" %>
21
- <% end %>
22
- </div>
23
-
24
- <% end %>
@@ -1,8 +0,0 @@
1
- json.extract! scaffolding_completely_concrete_tangible_things_reassignment,
2
- :id,
3
- :membership_id,
4
- :membership_ids,
5
- # 🚅 super scaffolding will insert new fields above this line.
6
- :created_at,
7
- :updated_at
8
- json.url account_memberships_reassignments_scaffolding_completely_concrete_tangible_things_reassignment_url(scaffolding_completely_concrete_tangible_things_reassignment, format: :json)
@@ -1 +0,0 @@
1
- json.array! @scaffolding_completely_concrete_tangible_things_reassignments, partial: "memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/scaffolding_completely_concrete_tangible_things_reassignment", as: :scaffolding_completely_concrete_tangible_things_reassignment
@@ -1,11 +0,0 @@
1
- <div class="element-wrapper">
2
- <h6 class="element-header"><%= t('.section') %></h6>
3
- <%= render 'account/shared/notices' %>
4
- <div class="padded-lg">
5
- <div class="element-box">
6
- <h5 class="form-header"><%= t('.header') %></h5>
7
- <div class="form-desc"><%= t('.description') %></div>
8
- <%= render 'form', scaffolding_completely_concrete_tangible_things_reassignment: @scaffolding_completely_concrete_tangible_things_reassignment %>
9
- </div>
10
- </div>
11
- </div>
@@ -1 +0,0 @@
1
- json.partial! "memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/scaffolding_completely_concrete_tangible_things_reassignment", scaffolding_completely_concrete_tangible_things_reassignment: @scaffolding_completely_concrete_tangible_things_reassignment
@@ -1,42 +0,0 @@
1
- en:
2
- memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments: &scaffolding_completely_concrete_tangible_things_reassignments
3
- label: &label Tangible Things Reassignments
4
- breadcrumbs:
5
- label: *label
6
- buttons: &buttons
7
- new: Reassign to Another Team Member
8
- create: Reassign Tangible Things
9
- fields: &fields
10
- membership_ids:
11
- name: &membership_ids Target Members
12
- label: Who should %{memberships_possessive} Tangible Things be reassigned to?
13
- heading: *membership_ids
14
- help: You can reassign to multiple team members. You can leave this blank to simply unassign Tangible Things from %{membership_name}.
15
- invite_help: You can invite new team members by entering their email address and hitting <kbd>enter</kbd>.
16
- # 🚅 super scaffolding will insert new fields above this line.
17
- index:
18
- section: "%{memberships_possessive} Tangible Things Reassignments"
19
- contexts:
20
- membership:
21
- header: Tangible Things Reassignments
22
- description: Below is a list of Tangible Things Reassignments that have been added to %{membership_name}. You can manage them with the options below.
23
- fields: *fields
24
- buttons: *buttons
25
- form: &form
26
- buttons: *buttons
27
- new:
28
- section: "Reassign %{memberships_possessive} Tangible Things"
29
- header: Reassign Tangible Things
30
- description: This will unassign all Tangible Things currently assigned to %{membership_name} and assign them to whichever team members are specified below.
31
- form: *form
32
- notifications:
33
- created: Tangible Things were successfully reassigned.
34
- account:
35
- memberships:
36
- reassignments:
37
- scaffolding_completely_concrete_tangible_things_reassignments: *scaffolding_completely_concrete_tangible_things_reassignments
38
- activerecord:
39
- attributes:
40
- memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignment:
41
- membership_ids: *membership_ids
42
- # 🚅 super scaffolding will insert new activerecord attributes above this line.