import { Button } from "@/src/components/ui/button"; import { api } from "@/src/utils/api"; import { useState } from "react"; import { PlusIcon } from "lucide-react"; import * as z from "zod/v4"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/src/components/ui/dialog"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/src/components/ui/form"; import { Select, SelectContent, SelectTrigger, SelectValue, } from "@/src/components/ui/select"; import { Input } from "@/src/components/ui/input"; import { Role } from "@langfuse/shared"; import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture"; import { useHasOrganizationAccess } from "@/src/features/rbac/utils/checkOrganizationAccess"; import { useHasEntitlement, useEntitlementLimit, } from "@/src/features/entitlements/hooks"; import { useHasProjectAccess } from "@/src/features/rbac/utils/checkProjectAccess"; import { RoleSelectItem } from "@/src/features/rbac/components/RoleSelectItem"; import { ActionButton } from "@/src/components/ActionButton"; const formSchema = z.object({ email: z.string().trim().email(), orgRole: z.enum(Role), projectRole: z.enum(Role), }); export function CreateProjectMemberButton(props: { orgId: string; project?: { id: string; name: string }; }) { const capture = usePostHogClientCapture(); const [open, setOpen] = useState(false); const hasOrgAccess = useHasOrganizationAccess({ organizationId: props.orgId, scope: "organizationMembers:CUD", }); const hasProjectAccess = useHasProjectAccess({ projectId: props.project?.id, scope: "projectMembers:CUD", }); const orgMemberLimit = useEntitlementLimit("organization-member-count"); const orgMemberCount = api.members.allFromOrg.useQuery( { orgId: props.orgId, page: 0, limit: 1, }, { enabled: hasOrgAccess, }, ).data?.totalCount; const inviteCount = api.members.allInvitesFromOrg.useQuery( { orgId: props.orgId, page: 0, limit: 1, }, { enabled: hasOrgAccess, }, ).data?.totalCount; const hasProjectRoleEntitlement = useHasEntitlement("rbac-project-roles"); const hasOnlySingleProjectAccess = !hasOrgAccess && hasProjectAccess && hasProjectRoleEntitlement; const utils = api.useUtils(); const mutCreateProjectMember = api.members.create.useMutation({ onSuccess: () => utils.members.invalidate(), onError: (error) => form.setError("email", { type: "manual", message: error.message, }), }); const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { email: "", orgRole: hasOnlySingleProjectAccess ? Role.NONE : Role.MEMBER, projectRole: hasOnlySingleProjectAccess ? Role.MEMBER : Role.NONE, }, }); function onSubmit(values: z.infer) { capture( props.project ? "project_settings:send_membership_invitation" : "organization_settings:send_membership_invitation", { orgRole: values.orgRole, projectRole: values.projectRole, }, ); return mutCreateProjectMember .mutateAsync({ orgId: props.orgId, email: values.email, orgRole: values.orgRole, //optional projectId: props.project?.id, projectRole: values.projectRole === Role.NONE ? undefined : values.projectRole, }) .then(() => { form.reset(); setOpen(false); }) .catch((error) => { console.error(error); }); } return ( <> Add new member to the{" "} {hasOnlySingleProjectAccess ? "project" : "organization"}
( Email )} /> {!hasOnlySingleProjectAccess && ( ( Organization Role )} /> )} {props.project !== undefined && hasProjectRoleEntitlement && ( ( Project Role {!hasOnlySingleProjectAccess && ( This project role will override the default role for this current project ({props.project!.name}). )} )} /> )}
); }