"use client"; import type { SessionContextValue } from "next-auth/react"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains"; import { MINIMUM_NUMBER_OF_ORG_SEATS } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import slugify from "@calcom/lib/slugify"; import { CreationSource } from "@calcom/prisma/enums"; import { UserPermissionRole } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; import type { Ensure } from "@calcom/types/utils"; import classNames from "@calcom/ui/classNames"; import { Alert } from "@calcom/ui/components/alert"; import { Button } from "@calcom/ui/components/button"; import { ToggleGroup } from "@calcom/ui/components/form"; import { Form } from "@calcom/ui/components/form"; import { Label } from "@calcom/ui/components/form"; import { TextField } from "@calcom/ui/components/form"; import { RadioAreaGroup as RadioArea } from "@calcom/ui/components/radio"; import { useOnboarding } from "../lib/onboardingStore"; function extractDomainFromEmail(email: string) { let out = ""; try { const match = email.match(/^(?:.*?:\/\/)?.*?(?[\w\-]*(?:\.\w{2,}|\.\w{2,}\.\w{2}))(?:[\/?#:]|$)/); out = (match && match.groups?.root) ?? ""; } catch (ignore) {} return out.split(".")[0]; } export const CreateANewOrganizationForm = () => { const session = useSession(); const { isLoadingOrgOnboarding } = useOnboarding({ step: "start" }); if (!session.data || isLoadingOrgOnboarding) { return null; } return ; }; enum BillingPeriod { MONTHLY = "MONTHLY", ANNUALLY = "ANNUALLY", } const CreateANewOrganizationFormChild = ({ session }: { session: Ensure }) => { const { t } = useLocale(); const router = useRouter(); const [serverErrorMessage, setServerErrorMessage] = useState(null); const isAdmin = session.data.user.role === UserPermissionRole.ADMIN; const defaultOrgOwnerEmail = session.data.user.email ?? ""; const { useOnboardingStore } = useOnboarding({ step: "start" }); const { slug, name, orgOwnerEmail, billingPeriod, pricePerSeat, seats, onboardingId, reset } = useOnboardingStore(); const newOrganizationFormMethods = useForm<{ name: string; seats: number | null; billingPeriod: BillingPeriod; pricePerSeat: number | null; slug: string; orgOwnerEmail: string; }>({ defaultValues: { billingPeriod: billingPeriod ?? BillingPeriod.MONTHLY, slug: slug ?? (!isAdmin ? deriveSlugFromEmail(defaultOrgOwnerEmail) : undefined), orgOwnerEmail: orgOwnerEmail || (!isAdmin ? defaultOrgOwnerEmail : undefined), name: name ?? (!isAdmin ? deriveOrgNameFromEmail(defaultOrgOwnerEmail) : undefined), seats: seats ?? null, pricePerSeat: pricePerSeat ?? null, }, }); const intentToCreateOrgMutation = trpc.viewer.organizations.intentToCreateOrg.useMutation({ onSuccess: async (data) => { // TODO: To be moved to _invoice.paid.org.ts // telemetry.event(telemetryEventTypes.org_created); reset({ onboardingId: data.organizationOnboardingId, billingPeriod: data.billingPeriod, pricePerSeat: data.pricePerSeat ?? null, seats: data.seats ?? null, orgOwnerEmail: data.orgOwnerEmail, name: data.name, slug: data.slug, }); if (isAdmin) { router.push("/settings/organizations/new/handover"); } else { router.push("/settings/organizations/new/about"); } }, onError: (err) => { if (err.message === "organization_url_taken") { newOrganizationFormMethods.setError("slug", { type: "custom", message: t("url_taken") }); } else if (err.message === "domain_taken_team" || err.message === "domain_taken_project") { newOrganizationFormMethods.setError("slug", { type: "custom", message: t("problem_registering_domain"), }); } else { setServerErrorMessage(t(err.message)); } }, }); const needToCreateOnboarding = !onboardingId; return ( <>
{ if (!needToCreateOnboarding) { router.push("/settings/organizations/new/about"); } else if (!intentToCreateOrgMutation.isPending) { setServerErrorMessage(null); intentToCreateOrgMutation.mutate({ ...v, creationSource: CreationSource.WEBAPP }); } }}>
{serverErrorMessage && (
)} {isAdmin && (
( <> { if ([BillingPeriod.ANNUALLY, BillingPeriod.MONTHLY].includes(e)) { onChange(e); } }} options={[ { value: "MONTHLY", label: "Monthly", }, { value: "ANNUALLY", label: "Annually", }, ]} /> )} />
)} (
{ const email = e?.target.value; newOrganizationFormMethods.setValue("orgOwnerEmail", email.trim()); if (newOrganizationFormMethods.getValues("slug") === "") { const slug = deriveSlugFromEmail(email); newOrganizationFormMethods.setValue("slug", slug); } const name = deriveOrgNameFromEmail(email); newOrganizationFormMethods.setValue("name", name); }} autoComplete="off" />
)} />
( <> { newOrganizationFormMethods.setValue("name", e?.target.value.trim()); if (newOrganizationFormMethods.formState.touchedFields["slug"] === undefined) { newOrganizationFormMethods.setValue("slug", slugify(e?.target.value)); } }} autoComplete="off" /> )} />
( { newOrganizationFormMethods.setValue("slug", slugify(e?.target.value), { shouldTouch: true, }); newOrganizationFormMethods.clearErrors("slug"); }} /> )} />
{isAdmin && ( <>
(
{ onChange(+e.target.value); }} autoComplete="off" />
)} />
(
{ onChange(+e.target.value); }} autoComplete="off" />
)} />
)} {/* This radio group does nothing - its just for visual purposes */} {!isAdmin && ( <>

Upgrade to Organizations

{t("teams")}

{t("your_current_plan")}

{t("organization")} {pricePerSeat && seats ? (

{`$${pricePerSeat} per user per month (${seats} seats minimum) ${ billingPeriod === BillingPeriod.ANNUALLY ? "(billed annually)" : "" }`}

) : (

{t("organization_price_per_user_month")}

)}
)}
); }; export function deriveSlugFromEmail(email: string) { const domain = extractDomainFromEmail(email); return domain; } export function deriveOrgNameFromEmail(email: string) { const domain = extractDomainFromEmail(email); return domain.charAt(0).toUpperCase() + domain.slice(1); }