mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 05:09:38 +02:00
Add/remove members and invitations (#1744)
* Add/remove members and invitations * Lint
This commit is contained in:
parent
282c05345d
commit
0696e1f2f7
10 changed files with 188 additions and 29 deletions
|
@ -34,6 +34,24 @@ class InvitationsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
unless Current.user.admin?
|
||||
flash[:alert] = t("invitations.destroy.not_authorized")
|
||||
redirect_to settings_profile_path
|
||||
return
|
||||
end
|
||||
|
||||
@invitation = Current.family.invitations.find(params[:id])
|
||||
|
||||
if @invitation.destroy
|
||||
flash[:notice] = t("invitations.destroy.success")
|
||||
else
|
||||
flash[:alert] = t("invitations.destroy.failure")
|
||||
end
|
||||
|
||||
redirect_to settings_profile_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def invitation_params
|
||||
|
|
|
@ -4,4 +4,28 @@ class Settings::ProfilesController < SettingsController
|
|||
@users = Current.family.users.order(:created_at)
|
||||
@pending_invitations = Current.family.invitations.pending
|
||||
end
|
||||
|
||||
def destroy
|
||||
unless Current.user.admin?
|
||||
flash[:alert] = t("settings.profiles.destroy.not_authorized")
|
||||
redirect_to settings_profile_path
|
||||
return
|
||||
end
|
||||
|
||||
@user = Current.family.users.find(params[:user_id])
|
||||
|
||||
if @user == Current.user
|
||||
flash[:alert] = t("settings.profiles.destroy.cannot_remove_self")
|
||||
redirect_to settings_profile_path
|
||||
return
|
||||
end
|
||||
|
||||
if @user.destroy
|
||||
flash[:notice] = t("settings.profiles.destroy.member_removed")
|
||||
else
|
||||
flash[:alert] = t("settings.profiles.destroy.member_removal_failed")
|
||||
end
|
||||
|
||||
redirect_to settings_profile_path
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,6 +43,21 @@
|
|||
<div class="rounded-md bg-gray-100 px-1.5 py-0.5">
|
||||
<p class="uppercase text-gray-500 font-medium text-xs"><%= user.role %></p>
|
||||
</div>
|
||||
<% if Current.user.admin? && user != Current.user %>
|
||||
<div class="ml-auto">
|
||||
<%= button_to settings_profile_path(user_id: user),
|
||||
method: :delete,
|
||||
class: "text-red-500 hover:text-red-700",
|
||||
data: { turbo_confirm: {
|
||||
title: t(".confirm_remove_member.title"),
|
||||
body: t(".confirm_remove_member.body", name: user.display_name),
|
||||
accept: t(".remove_member"),
|
||||
acceptClass: "w-full bg-red-500 text-white rounded-xl text-center p-[10px] border mb-2"
|
||||
}} do %>
|
||||
<%= lucide_icon "x", class: "w-5 h-5" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @pending_invitations.any? %>
|
||||
|
@ -59,6 +74,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<% if self_hosted? %>
|
||||
<div class="flex items-center gap-2" data-controller="clipboard">
|
||||
<p class="text-gray-500 text-sm"><%= t(".invitation_link") %></p>
|
||||
|
@ -78,6 +94,20 @@
|
|||
</button>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if Current.user.admin? %>
|
||||
<%= button_to invitation_path(invitation),
|
||||
method: :delete,
|
||||
class: "text-red-500 hover:text-red-700",
|
||||
data: { turbo_confirm: {
|
||||
title: t(".confirm_remove_invitation.title"),
|
||||
body: t(".confirm_remove_invitation.body", email: invitation.email),
|
||||
accept: t(".remove_invitation"),
|
||||
acceptClass: "w-full bg-red-500 text-white rounded-xl text-center p-[10px] border mb-2"
|
||||
}} do %>
|
||||
<%= lucide_icon "x", class: "w-5 h-5" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -3,7 +3,7 @@ if ENV["SENTRY_DSN"].present?
|
|||
config.dsn = ENV["SENTRY_DSN"]
|
||||
config.environment = ENV["RAILS_ENV"]
|
||||
config.breadcrumbs_logger = [ :active_support_logger, :http_logger ]
|
||||
config.enabled_environments = %w[development production]
|
||||
config.enabled_environments = %w[production]
|
||||
|
||||
# Set traces_sample_rate to 1.0 to capture 100%
|
||||
# of transactions for performance monitoring.
|
||||
|
|
|
@ -4,6 +4,10 @@ en:
|
|||
create:
|
||||
failure: Could not send invitation
|
||||
success: Invitation sent successfully
|
||||
destroy:
|
||||
not_authorized: You are not authorized to manage invitations.
|
||||
success: Invitation was successfully removed.
|
||||
failure: There was a problem removing the invitation.
|
||||
new:
|
||||
email_label: Email Address
|
||||
email_placeholder: Enter email address
|
||||
|
|
|
@ -48,11 +48,24 @@ en:
|
|||
theme_title: Theme
|
||||
timezone: Timezone
|
||||
profiles:
|
||||
destroy:
|
||||
not_authorized: You are not authorized to remove members.
|
||||
cannot_remove_self: You cannot remove yourself from the account.
|
||||
member_removed: Member was successfully removed.
|
||||
member_removal_failed: There was a problem removing the member.
|
||||
show:
|
||||
confirm_delete:
|
||||
body: Are you sure you want to permanently delete your account? This action
|
||||
is irreversible.
|
||||
title: Delete account?
|
||||
confirm_remove_member:
|
||||
title: Remove Member
|
||||
body: Are you sure you want to remove %{name} from your account?
|
||||
remove_member: Remove Member
|
||||
confirm_remove_invitation:
|
||||
title: Remove Invitation
|
||||
body: Are you sure you want to remove the invitation for %{email}?
|
||||
remove_invitation: Remove Invitation
|
||||
danger_zone_title: Danger Zone
|
||||
delete_account: Delete account
|
||||
delete_account_warning: Deleting your account will permanently remove all
|
||||
|
|
|
@ -20,7 +20,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
|
||||
namespace :settings do
|
||||
resource :profile, only: :show
|
||||
resource :profile, only: [ :show, :destroy ]
|
||||
resource :preferences, only: :show
|
||||
resource :hosting, only: %i[show update]
|
||||
resource :billing, only: :show
|
||||
|
@ -142,7 +142,7 @@ Rails.application.routes.draw do
|
|||
resources :exchange_rate_provider_missings, only: :update
|
||||
end
|
||||
|
||||
resources :invitations, only: [ :new, :create ] do
|
||||
resources :invitations, only: [ :new, :create, :destroy ] do
|
||||
get :accept, on: :member
|
||||
end
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ require "test_helper"
|
|||
|
||||
class InvitationsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in @user = users(:family_admin)
|
||||
sign_in @admin = users(:family_admin)
|
||||
@invitation = invitations(:one)
|
||||
end
|
||||
|
||||
|
@ -25,7 +25,7 @@ class InvitationsControllerTest < ActionDispatch::IntegrationTest
|
|||
|
||||
invitation = Invitation.order(created_at: :desc).first
|
||||
assert_equal "member", invitation.role
|
||||
assert_equal @user, invitation.inviter
|
||||
assert_equal @admin, invitation.inviter
|
||||
assert_equal "new@example.com", invitation.email
|
||||
assert_redirected_to settings_profile_path
|
||||
assert_equal I18n.t("invitations.create.success"), flash[:notice]
|
||||
|
@ -59,8 +59,8 @@ class InvitationsControllerTest < ActionDispatch::IntegrationTest
|
|||
|
||||
invitation = Invitation.order(created_at: :desc).first
|
||||
assert_equal "admin", invitation.role
|
||||
assert_equal @user.family, invitation.family
|
||||
assert_equal @user, invitation.inviter
|
||||
assert_equal @admin.family, invitation.family
|
||||
assert_equal @admin, invitation.inviter
|
||||
end
|
||||
|
||||
test "should handle invalid invitation creation" do
|
||||
|
@ -86,4 +86,29 @@ class InvitationsControllerTest < ActionDispatch::IntegrationTest
|
|||
get accept_invitation_url("invalid-token")
|
||||
assert_response :not_found
|
||||
end
|
||||
|
||||
test "admin can remove pending invitation" do
|
||||
assert_difference("Invitation.count", -1) do
|
||||
delete invitation_url(@invitation)
|
||||
end
|
||||
|
||||
assert_redirected_to settings_profile_path
|
||||
assert_equal I18n.t("invitations.destroy.success"), flash[:notice]
|
||||
end
|
||||
|
||||
test "non-admin cannot remove invitations" do
|
||||
sign_in users(:family_member)
|
||||
|
||||
assert_no_difference("Invitation.count") do
|
||||
delete invitation_url(@invitation)
|
||||
end
|
||||
|
||||
assert_redirected_to settings_profile_path
|
||||
assert_equal I18n.t("invitations.destroy.not_authorized"), flash[:alert]
|
||||
end
|
||||
|
||||
test "should handle invalid invitation removal" do
|
||||
delete invitation_url(id: "invalid-id")
|
||||
assert_response :not_found
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,46 @@ require "test_helper"
|
|||
|
||||
class Settings::ProfilesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in @user = users(:family_admin)
|
||||
@admin = users(:family_admin)
|
||||
@member = users(:family_member)
|
||||
end
|
||||
|
||||
test "get" do
|
||||
get settings_profile_url
|
||||
test "should get show" do
|
||||
sign_in @admin
|
||||
get settings_profile_path
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "admin can remove a family member" do
|
||||
sign_in @admin
|
||||
assert_difference("User.count", -1) do
|
||||
delete settings_profile_path(user_id: @member)
|
||||
end
|
||||
|
||||
assert_redirected_to settings_profile_path
|
||||
assert_equal I18n.t("settings.profiles.destroy.member_removed"), flash[:notice]
|
||||
assert_raises(ActiveRecord::RecordNotFound) { User.find(@member.id) }
|
||||
end
|
||||
|
||||
test "admin cannot remove themselves" do
|
||||
sign_in @admin
|
||||
assert_no_difference("User.count") do
|
||||
delete settings_profile_path(user_id: @admin)
|
||||
end
|
||||
|
||||
assert_redirected_to settings_profile_path
|
||||
assert_equal I18n.t("settings.profiles.destroy.cannot_remove_self"), flash[:alert]
|
||||
assert User.find(@admin.id)
|
||||
end
|
||||
|
||||
test "non-admin cannot remove members" do
|
||||
sign_in @member
|
||||
assert_no_difference("User.count") do
|
||||
delete settings_profile_path(user_id: @admin)
|
||||
end
|
||||
|
||||
assert_redirected_to settings_profile_path
|
||||
assert_equal I18n.t("settings.profiles.destroy.not_authorized"), flash[:alert]
|
||||
assert User.find(@admin.id)
|
||||
end
|
||||
end
|
||||
|
|
10
test/fixtures/invitations.yml
vendored
10
test/fixtures/invitations.yml
vendored
|
@ -17,3 +17,13 @@ two:
|
|||
created_at: <%= Time.current %>
|
||||
updated_at: <%= Time.current %>
|
||||
expires_at: <%= 3.days.from_now %>
|
||||
|
||||
other_family:
|
||||
email: "other@example.com"
|
||||
token: "valid-token-789"
|
||||
role: "member"
|
||||
inviter: empty
|
||||
family: empty
|
||||
created_at: <%= Time.current %>
|
||||
updated_at: <%= Time.current %>
|
||||
expires_at: <%= 3.days.from_now %>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue