bullet_train 1.0.0 → 1.0.4

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