Skip to content

Commit

Permalink
Allow admins to invite other staff (#251)
Browse files Browse the repository at this point in the history
* Allow admins to invite other staff

* Add invite staff test

* Implement CR remarks
  • Loading branch information
marlena-b authored Oct 13, 2023
1 parent 5b428c0 commit e514793
Show file tree
Hide file tree
Showing 20 changed files with 310 additions and 100 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ gem "bootstrap_form", "~> 5.3"
# Devise Authentication
gem "devise"

gem "devise_invitable", "~> 2.0.0"

# Use Sass to process CSS
gem "dartsass-rails"

Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise_invitable (2.0.8)
actionmailer (>= 5.0)
devise (>= 4.6)
docile (1.4.0)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
Expand Down Expand Up @@ -478,6 +481,7 @@ DEPENDENCIES
dartsass-rails
debug
devise
devise_invitable (~> 2.0.0)
evil_systems (~> 1.1)
factory_bot_rails
faker
Expand Down
30 changes: 30 additions & 0 deletions app/controllers/organizations/invitations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class Organizations::InvitationsController < Devise::InvitationsController
before_action :require_organization_admin, only: [:new, :create]
layout "dashboard", only: [:new, :create]

def new
@user = User.new
@staff = StaffAccount.new(user: @user)
end

def create
@user = User.new(user_params.merge(password: SecureRandom.hex(8)).except(:staff_account_attributes))
@user.staff_account = StaffAccount.new(verified: true)

if @user.save
@user.staff_account.add_role(user_params[:staff_account_attributes][:roles])
@user.invite!(current_user)
redirect_to staff_index_path, notice: "Invite sent!"
else
render :new, status: :unprocessable_entity
end
end

private

def user_params
params.require(:user)
.permit(:first_name, :last_name, :email,
staff_account_attributes: [:roles])
end
end
25 changes: 0 additions & 25 deletions app/controllers/organizations/staff_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,4 @@ class Organizations::StaffController < Organizations::BaseController
def index
@staff_accounts = StaffAccount.all
end

def new
@user = User.new
@staff = StaffAccount.new(user: @user)
end

def create
@user = User.new(user_params.merge(password: SecureRandom.hex(8)).except(:staff_account_attributes))
@user.staff_account = StaffAccount.new

if @user.save
@user.staff_account.add_role(user_params[:staff_account_attributes][:roles])
redirect_to staff_index_path, notice: "Staff saved successfully."
else
render :new, status: :unprocessable_entity
end
end

private

def user_params
params.require(:user)
.permit(:first_name, :last_name, :email,
staff_account_attributes: [:roles])
end
end
1 change: 1 addition & 0 deletions app/models/pet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# name :string
# pause_reason :integer default("not_paused")
# sex :string
# species :integer not null
# weight_from :integer not null
# weight_to :integer not null
# weight_unit :string not null
Expand Down
13 changes: 12 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,29 @@
# email :string default(""), not null
# encrypted_password :string default(""), not null
# first_name :string not null
# invitation_accepted_at :datetime
# invitation_created_at :datetime
# invitation_limit :integer
# invitation_sent_at :datetime
# invitation_token :string
# invitations_count :integer default(0)
# invited_by_type :string
# last_name :string not null
# remember_created_at :datetime
# reset_password_sent_at :datetime
# reset_password_token :string
# tos_agreement :boolean
# created_at :datetime not null
# updated_at :datetime not null
# invited_by_id :bigint
# organization_id :bigint
#
# Indexes
#
# index_users_on_email (email) UNIQUE
# index_users_on_invitation_token (invitation_token) UNIQUE
# index_users_on_invited_by (invited_by_type,invited_by_id)
# index_users_on_invited_by_id (invited_by_id)
# index_users_on_organization_id (organization_id)
# index_users_on_reset_password_token (reset_password_token) UNIQUE
#
Expand All @@ -35,7 +46,7 @@ class User < ApplicationRecord
end
end

devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable
devise :invitable, :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable

validates :first_name, presence: true
validates :last_name, presence: true
Expand Down
39 changes: 39 additions & 0 deletions app/views/organizations/invitations/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!-- form -->
<%= bootstrap_form_with model: user, url: invitation_path(user) do |form| %>

<div class="row gx-3">
<!-- form group -->
<div class="mb-3 col-md-6 col-12">
<div class="input-group me-3">
<%= form.text_field :first_name, class: 'form-control', required: true %>
</div>
</div>
<!-- form group -->
<div class="mb-3 col-md-6 col-12">
<div class="input-group me-3">
<%= form.text_field :last_name, class: 'form-control', required: true %>
</div>
</div>
<!-- form group -->
<div class="mb-3 col-12">
<div class="input-group">
<%= form.text_field :email, class: 'form-control', required: true %>
</div>
</div>

<!-- form group -->
<%= form.fields_for :staff_account do |staff_subform| %>
<div class="mb-3 col-md-6 col-12">
<div class="input-group me-3">
<%= staff_subform.select :roles, options_for_select([['Staff', :staff], ['Admin', :admin]]), class: 'form-control', required: true %>
</div>
</div>
<% end %>

<!-- button -->
<div class="col-12 mt-3">
<%= form.submit 'Send invite', class: 'btn btn-primary' %>
</div>
</div>

<% end %>
21 changes: 21 additions & 0 deletions app/views/organizations/invitations/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<h2><%= t "devise.invitations.edit.header" %></h2>

<%= form_for(resource, as: resource_name, url: invitation_path(resource_name), html: { method: :put }) do |f| %>
<%= f.hidden_field :invitation_token, readonly: true %>

<% if f.object.class.require_password_on_accepting %>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password %>
</div>

<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %>
</div>
<% end %>

<div class="actions">
<%= f.submit t("devise.invitations.edit.submit_button") %>
</div>
<% end %>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<section class="container-fluid p-4">

<!--page heading-->
<%= provide(:header_title, "New staff") %>
<%= provide(:header_title, "Invite staff") %>

<%= content_for :button do %>
<%= link_to "Back to staff", staff_index_path, class: "btn btn-primary" %><br>
Expand All @@ -23,4 +23,4 @@
</div>
</div>
</div>
</section>
</section>
11 changes: 11 additions & 0 deletions app/views/organizations/mailer/invitation_instructions.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<p><%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %></p>

<p><%= t("devise.mailer.invitation_instructions.someone_invited_you", url: root_url) %></p>

<p><%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, invitation_token: @token) %></p>

<% if @resource.invitation_due_at %>
<p><%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %></p>
<% end %>

<p><%= t("devise.mailer.invitation_instructions.ignore") %></p>
11 changes: 11 additions & 0 deletions app/views/organizations/mailer/invitation_instructions.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %>

<%= t("devise.mailer.invitation_instructions.someone_invited_you", url: root_url) %>

<%= accept_invitation_url(@resource, invitation_token: @token) %>

<% if @resource.invitation_due_at %>
<%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %>
<% end %>

<%= t("devise.mailer.invitation_instructions.ignore") %>
68 changes: 0 additions & 68 deletions app/views/organizations/staff/_form.html.erb

This file was deleted.

4 changes: 2 additions & 2 deletions app/views/organizations/staff/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<!--create staff button-->
<%= content_for :button do %>
<%= link_to "New Staff", new_staff_path, class: "btn btn-primary" %><br>
<%= link_to "Invite Staff", new_user_invitation_path, class: "btn btn-primary" %><br>
<% end %>

<section class="container-fluid p-4">
Expand Down Expand Up @@ -140,4 +140,4 @@
</div>
</div>
</div>
</section>
</section>
49 changes: 49 additions & 0 deletions config/initializers/devise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,55 @@
# Send a notification email when the user's password is changed.
# config.send_password_change_notification = false

# ==> Configuration for :invitable
# The period the generated invitation token is valid.
# After this period, the invited resource won't be able to accept the invitation.
# When invite_for is 0 (the default), the invitation won't expire.
# config.invite_for = 2.weeks

# Number of invitations users can send.
# - If invitation_limit is nil, there is no limit for invitations, users can
# send unlimited invitations, invitation_limit column is not used.
# - If invitation_limit is 0, users can't send invitations by default.
# - If invitation_limit n > 0, users can send n invitations.
# You can change invitation_limit column for some users so they can send more
# or less invitations, even with global invitation_limit = 0
# Default: nil
# config.invitation_limit = 5

# The key to be used to check existing users when sending an invitation
# and the regexp used to test it when validate_on_invite is not set.
# config.invite_key = { email: /\A[^@]+@[^@]+\z/ }
# config.invite_key = { email: /\A[^@]+@[^@]+\z/, username: nil }

# Ensure that invited record is valid.
# The invitation won't be sent if this check fails.
# Default: false
# config.validate_on_invite = true

# Resend invitation if user with invited status is invited again
# Default: true
# config.resend_invitation = false

# The class name of the inviting model. If this is nil,
# the #invited_by association is declared to be polymorphic.
# Default: nil
# config.invited_by_class_name = 'User'

# The foreign key to the inviting model (if invited_by_class_name is set)
# Default: :invited_by_id
# config.invited_by_foreign_key = :invited_by_id

# The column name used for counter_cache column. If this is nil,
# the #invited_by association is declared without counter_cache.
# Default: nil
# config.invited_by_counter_cache = :invitations_count

# Auto-login after the user accepts the invite. If this is false,
# the user will need to manually log in after accepting the invite.
# Default: true
# config.allow_insecure_sign_in_after_accept = false

# ==> Configuration for :confirmable
# A period that the user is allowed to access the website even without
# confirming their account. For instance, if set to 2.days, the user will be
Expand Down
Loading

0 comments on commit e514793

Please sign in to comment.