import { useFlagMap } from "@calcom/features/flags/context/provider"; import { CreationSource, MembershipRole } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; import { useRouter } from "next/navigation"; import { useState } from "react"; import type { OnboardingState } from "../store/onboarding-store"; import { useOnboardingStore } from "../store/onboarding-store"; type UseCreateTeamOptions = { redirectBasePath?: string; skipRedirectAfterInvite?: boolean; isOnboarding?: boolean; billingPeriod?: "MONTHLY" | "ANNUALLY"; }; export function useCreateTeam(options: UseCreateTeamOptions = {}) { const { redirectBasePath = "/onboarding/teams", skipRedirectAfterInvite = false, isOnboarding = true, billingPeriod, } = options; const router = useRouter(); const [isSubmitting, setIsSubmitting] = useState(false); const flags = useFlagMap(); const { setTeamId, teamId } = useOnboardingStore(); const createTeamMutation = trpc.viewer.teams.create.useMutation(); const inviteMemberMutation = trpc.viewer.teams.inviteMember.useMutation(); const createTeam = async (store?: OnboardingState) => { setIsSubmitting(true); try { // Get the latest state from the store to ensure we have the most up-to-date values const currentStore = store || useOnboardingStore.getState(); const { teamDetails, teamBrand } = currentStore; // Validate team details - if empty, redirect back to team details step if (!teamDetails.name || !teamDetails.name.trim() || !teamDetails.slug || !teamDetails.slug.trim()) { router.push(`${redirectBasePath}/details`); setIsSubmitting(false); return; } // Create the team const result = await createTeamMutation.mutateAsync({ name: teamDetails.name, slug: teamDetails.slug, bio: teamDetails.bio, logo: teamBrand.logo, isOnboarding, ...(billingPeriod && { billingPeriod }), }); // If there's a checkout URL, redirect to Stripe payment if (result.url && !result.team) { window.location.href = result.url; return; } if (result.team) { // Store the teamId and redirect to invite flow after team creation setTeamId(result.team.id); router.push(`${redirectBasePath}/invite/email?teamId=${result.team.id}`); } } catch (error) { console.error("Failed to create team:", error); throw error; } finally { setIsSubmitting(false); } }; const inviteMembers = async ( invites: Array<{ email: string; role: "MEMBER" | "ADMIN" }>, language: string ) => { if (!teamId) { throw new Error("Team ID is required to invite members"); } setIsSubmitting(true); try { // Filter and validate invites const validInvites = invites.filter((invite) => invite.email && invite.email.trim().length > 0); if (validInvites.length === 0) { throw new Error("At least one valid email address is required"); } // Group invites by role and send separate requests for each role // This is necessary because the schema validation expects array of strings when using bulk invites const invitesByRole = validInvites.reduce( (acc, invite) => { const role = invite.role === "ADMIN" ? MembershipRole.ADMIN : MembershipRole.MEMBER; if (!acc[role]) { acc[role] = []; } acc[role].push(invite.email.trim().toLowerCase()); return acc; }, {} as Record ); // Send invites for each role group await Promise.all( Object.entries(invitesByRole).map(([role, emails]) => inviteMemberMutation.mutateAsync({ teamId, usernameOrEmail: emails, // Array of strings, not objects role: role as MembershipRole, language, creationSource: CreationSource.WEBAPP, }) ) ); // Redirect to personal settings after successful invite (unless caller handles its own redirect) if (!skipRedirectAfterInvite) { const gettingStartedPath = flags["onboarding-v3"] ? "/onboarding/personal/settings?fromTeamOnboarding=true" : "/getting-started"; router.replace(gettingStartedPath); } } catch (error) { console.error("Failed to invite members:", error); // Extract error message from TRPC error if (error && typeof error === "object" && "message" in error) { throw new Error(error.message as string); } if (error instanceof Error) { throw error; } throw new Error("Failed to invite members. Please try again."); } finally { setIsSubmitting(false); } }; return { createTeam, inviteMembers, isSubmitting, error: createTeamMutation.error || inviteMemberMutation.error, }; }