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