import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import toast from 'react-hot-toast' import * as z from 'zod' import Link from 'next/link' import { useParams } from 'common' import { useOrganizationCreateInvitationMutation } from 'data/organization-members/organization-invitation-create-mutation' import { useOrganizationRolesV2Query } from 'data/organization-members/organization-roles-query' import { useOrganizationMemberInviteCreateMutation } from 'data/organizations/organization-member-invite-create-mutation' import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query' import { useProjectsQuery } from 'data/projects/projects-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { doPermissionsCheck, useGetPermissions, useCheckPermissions, } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useFlag } from 'hooks/ui/useFlag' import { useProfile } from 'lib/profile' import { isNil } from 'lodash' import { Button, Dialog, DialogContent, DialogHeader, DialogSection, DialogSectionSeparator, DialogTitle, DialogTrigger, FormControl_Shadcn_, FormDescription_Shadcn_, FormField_Shadcn_, FormItem_Shadcn_, FormLabel_Shadcn_, FormMessage_Shadcn_, Form_Shadcn_, Input_Shadcn_, SelectContent_Shadcn_, SelectGroup_Shadcn_, SelectItem_Shadcn_, SelectTrigger_Shadcn_, Select_Shadcn_, Switch, TooltipContent_Shadcn_, TooltipTrigger_Shadcn_, Tooltip_Shadcn_, } from 'ui' import { useGetRolesManagementPermissions } from './TeamSettings.utils' import InformationBox from 'components/ui/InformationBox' export const InviteMemberButton = () => { const { slug } = useParams() const { profile } = useProfile() const organization = useSelectedOrganization() const { permissions: permissions } = useGetPermissions() const projectLevelPermissionsEnabled = useFlag('projectLevelPermissions') const [isOpen, setIsOpen] = useState(false) const { data: projects } = useProjectsQuery() const { data: members } = useOrganizationMembersQuery({ slug }) const { data: allRoles, isSuccess } = useOrganizationRolesV2Query({ slug }) const orgScopedRoles = (allRoles?.org_scoped_roles ?? []).sort( (a, b) => b.base_role_id - a.base_role_id ) const canReadSubscriptions = useCheckPermissions( PermissionAction.BILLING_READ, 'stripe.subscriptions' ) const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery( { orgSlug: slug }, { enabled: canReadSubscriptions } ) const currentPlan = subscription?.plan const userMemberData = members?.find((m) => m.gotrue_id === profile?.gotrue_id) const hasOrgRole = (userMemberData?.role_ids ?? []).length === 1 && orgScopedRoles.some((r) => r.id === userMemberData?.role_ids[0]) const { rolesAddable } = useGetRolesManagementPermissions( organization?.id, orgScopedRoles, permissions ?? [] ) const canInviteMembers = hasOrgRole && orgScopedRoles.some(({ id: role_id }) => doPermissionsCheck( permissions, PermissionAction.CREATE, 'user_invites', { resource: { role_id } }, organization?.id ) ) const { mutate: inviteMember, isLoading: isInviting } = useOrganizationCreateInvitationMutation() const { mutate: inviteMemberOld, isLoading: isInvitingOld } = useOrganizationMemberInviteCreateMutation() const FormSchema = z.object({ email: z.string().email('Must be a valid email address').min(1, 'Email is required'), role: z.string().min(1, 'Role is required'), applyToOrg: z.boolean(), projectRef: z.string(), }) const form = useForm>({ mode: 'onBlur', reValidateMode: 'onBlur', resolver: zodResolver(FormSchema), defaultValues: { email: '', role: '', applyToOrg: true, projectRef: '' }, }) const { applyToOrg } = form.watch() const onInviteMember = async (values: z.infer) => { if (!slug) return console.error('Slug is required') if (profile?.id === undefined) return console.error('Profile ID required') const developerRole = orgScopedRoles.find((role) => role.name === 'Developer') const existingMember = (members ?? []).find( (member) => member.primary_email === values.email.toLowerCase() ) if (existingMember !== undefined) { if (existingMember.invited_id) { return toast('User has already been invited to this organization') } else { return toast('User is already in this organization') } } if (projectLevelPermissionsEnabled) { inviteMember( { slug, email: values.email.toLowerCase(), roleId: Number(values.role), ...(!values.applyToOrg && values.projectRef ? { projects: [values.projectRef] } : {}), }, { onSuccess: () => { toast.success('Successfully sent invitation to new member') setIsOpen(!isOpen) form.reset({ email: '', role: developerRole?.id.toString() ?? '', applyToOrg: true, projectRef: '', }) }, } ) } else { inviteMemberOld( { slug, invitedEmail: values.email.toLowerCase(), ownerId: profile.id, roleId: Number(values.role), }, { onSuccess: (data) => { if (isNil(data)) { toast.error('Failed to add member') } else { toast.success('Successfully added new member') setIsOpen(!isOpen) form.reset({ email: '', role: developerRole?.id.toString() ?? '', applyToOrg: true, projectRef: '', }) } }, } ) } } useEffect(() => { if (isSuccess && isOpen) { const developerRole = orgScopedRoles.find((role) => role.name === 'Developer') if (developerRole !== undefined) form.setValue('role', developerRole.id.toString()) } }, [isSuccess, isOpen]) useEffect(() => { if (!applyToOrg) { const firstProject = projects?.[0] if (firstProject !== undefined) form.setValue('projectRef', firstProject.ref) } else { form.setValue('projectRef', '') } }, [applyToOrg]) return ( {!canInviteMembers && ( You need additional permissions to invite a member to this organization )} Invite a member to this organization
{projectLevelPermissionsEnabled && ( ( form.setValue('applyToOrg', value)} /> Apply role to all projects in the organization )} /> )} ( Member role form.setValue('role', value)} > {orgScopedRoles.find((role) => role.id === Number(field.value))?.name ?? 'Unknown'} {orgScopedRoles.map((role) => { const canAssignRole = rolesAddable.includes(role.id) return ( {role.name} {!canAssignRole && ( Additional permissions required to assign role )} ) })} )} /> {!applyToOrg && ( ( Select a project form.setValue('projectRef', value)} > {(projects ?? []).find((project) => project.ref === field.value) ?.name ?? 'Unknown'} {(projects ?? []).map((project) => { return ( {project.name} ) })} You can assign roles to multiple projects once the invite is accepted )} /> )} ( Email address )} />

Supabase offers single sign-on (SSO) as a login option to provide additional account security for your team. This allows company administrators to enforce the use of an identity provider when logging into Supabase.

This is only available for organizations on teams plan or above.

{isSuccessSubscription && (currentPlan?.id === 'free' || currentPlan?.id === 'pro') && ( )}
} />
) }