LeaveTeamButton.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import { LOCAL_STORAGE_KEYS, useParams } from 'common'
  2. import { useRouter } from 'next/router'
  3. import { useState } from 'react'
  4. import { toast } from 'sonner'
  5. import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
  6. import { hasMultipleOwners } from './TeamSettings.utils'
  7. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  8. import { useOrganizationRolesV2Query } from '@/data/organization-members/organization-roles-query'
  9. import { useOrganizationMemberDeleteMutation } from '@/data/organizations/organization-member-delete-mutation'
  10. import { useOrganizationMembersQuery } from '@/data/organizations/organization-members-query'
  11. import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
  12. import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
  13. import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
  14. import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
  15. import { useProfile } from '@/lib/profile'
  16. export const LeaveTeamButton = () => {
  17. const router = useRouter()
  18. const { slug } = useParams()
  19. const { profile } = useProfile()
  20. const { data: selectedOrganization } = useSelectedOrganizationQuery()
  21. // if organizationMembersDeletionEnabled is false, you also can't delete yourself
  22. const { organizationMembersDelete: organizationMembersDeletionEnabled } = useIsFeatureEnabled([
  23. 'organization_members:delete',
  24. ])
  25. const [isLeaving, setIsLeaving] = useState(false)
  26. const [isLeaveTeamModalOpen, setIsLeaveTeamModalOpen] = useState(false)
  27. const [_, setLastVisitedOrganization] = useLocalStorageQuery(
  28. LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
  29. ''
  30. )
  31. const { refetch: refetchOrganizations } = useOrganizationsQuery()
  32. const { data: members } = useOrganizationMembersQuery({ slug })
  33. const { data: allRoles } = useOrganizationRolesV2Query({ slug })
  34. const roles = allRoles?.org_scoped_roles ?? []
  35. const currentUserMember = members?.find((member) => member.gotrue_id === profile?.gotrue_id)
  36. const currentUserRoleId = currentUserMember?.role_ids?.[0]
  37. const currentUserRole = roles.find((role) => role.id === currentUserRoleId)
  38. const isAdmin = currentUserRole?.name === 'Administrator'
  39. const isOwner = selectedOrganization?.is_owner
  40. const canLeave = !isOwner || (isOwner && hasMultipleOwners(members, roles))
  41. const { mutate: deleteMember } = useOrganizationMemberDeleteMutation({
  42. onSuccess: async () => {
  43. setIsLeaving(false)
  44. setIsLeaveTeamModalOpen(false)
  45. await refetchOrganizations()
  46. toast.success(`Successfully left ${selectedOrganization?.name}`)
  47. setLastVisitedOrganization('')
  48. router.push('/organizations')
  49. },
  50. onError: (error) => {
  51. setIsLeaving(false)
  52. toast.error(`Failed to leave organization: ${error?.message}`)
  53. },
  54. })
  55. const leaveTeam = async () => {
  56. if (!slug) return console.error('Org slug is required')
  57. if (!profile) return console.error('Profile is required')
  58. setIsLeaving(true)
  59. deleteMember({ slug, gotrueId: profile.gotrue_id })
  60. }
  61. return (
  62. <>
  63. <ButtonTooltip
  64. type="default"
  65. disabled={!canLeave || !organizationMembersDeletionEnabled || isLeaving}
  66. onClick={() => setIsLeaveTeamModalOpen(true)}
  67. tooltip={{
  68. content: {
  69. side: 'bottom',
  70. text: !canLeave
  71. ? 'An organization requires at least 1 owner'
  72. : !organizationMembersDeletionEnabled
  73. ? 'Unable to leave organization'
  74. : undefined,
  75. },
  76. }}
  77. >
  78. Leave team
  79. </ButtonTooltip>
  80. <ConfirmationModal
  81. size="medium"
  82. visible={isLeaveTeamModalOpen}
  83. title="Confirm to leave organization"
  84. confirmLabel="Leave"
  85. variant="warning"
  86. alert={{
  87. title: 'All of your user content will be permanently removed.',
  88. description: (
  89. <div>
  90. <p>
  91. Leaving the organization will delete all of your saved content in the projects of
  92. the organization, which includes:
  93. </p>
  94. <ul className="list-disc pl-4">
  95. <li>
  96. SQL snippets <span className="text-foreground">(both private and shared)</span>
  97. </li>
  98. <li>Custom reports</li>
  99. <li>Log Explorer queries</li>
  100. </ul>
  101. {(isOwner || isAdmin) && (
  102. <div className="mt-2">
  103. <p>
  104. <span className="text-foreground">
  105. Leaving won't remove your payment method or stop payments.
  106. </span>
  107. </p>
  108. <ul className="list-disc pl-4">
  109. <li>
  110. The current payment method will remain active and may still be charged after
  111. you leave.
  112. </li>
  113. <li>The billing address will remain unchanged.</li>
  114. </ul>
  115. </div>
  116. )}
  117. </div>
  118. ),
  119. }}
  120. onCancel={() => setIsLeaveTeamModalOpen(false)}
  121. onConfirm={() => leaveTeam()}
  122. >
  123. <p className="text-sm text-foreground-light">
  124. Are you sure you want to leave this organization? This is permanent.
  125. </p>
  126. </ConfirmationModal>
  127. </>
  128. )
  129. }