"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { signIn } from "next-auth/react"; import { Trans } from "next-i18next"; import dynamic from "next/dynamic"; import Link from "next/link"; import { useRouter } from "next/navigation"; import Script from "next/script"; import { useState, useEffect } from "react"; import type { SubmitHandler } from "react-hook-form"; import { useForm, useFormContext } from "react-hook-form"; import { Toaster } from "react-hot-toast"; import { z } from "zod"; import getStripe from "@calcom/app-store/stripepayment/lib/client"; import { getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils"; import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail"; import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; import { classNames } from "@calcom/lib"; import { APP_NAME, URL_PROTOCOL_REGEX, IS_CALCOM, WEBAPP_URL, WEBSITE_URL, CLOUDFLARE_SITE_ID, } from "@calcom/lib/constants"; import { fetchUsername } from "@calcom/lib/fetchUsername"; import { pushGTMEvent } from "@calcom/lib/gtm"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useDebounce } from "@calcom/lib/hooks/useDebounce"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { signupSchema as apiSignupSchema } from "@calcom/prisma/zod-utils"; import type { inferSSRProps } from "@calcom/types/inferSSRProps"; import { Button, HeadSeo, PasswordField, TextField, Form, Alert, showToast, CheckboxField, Icon, } from "@calcom/ui"; import { getServerSideProps } from "@lib/signup/getServerSideProps"; import PageWrapper from "@components/PageWrapper"; const signupSchema = apiSignupSchema.extend({ apiError: z.string().optional(), // Needed to display API errors doesnt get passed to the API cfToken: z.string().optional(), }); const TurnstileCaptcha = dynamic(() => import("@components/auth/Turnstile"), { ssr: false }); type FormValues = z.infer; export type SignupProps = inferSSRProps; const FEATURES = [ { title: "connect_all_calendars", description: "connect_all_calendars_description", i18nOptions: { appName: APP_NAME, }, icon: "calendar-heart" as const, }, { title: "set_availability", description: "set_availbility_description", icon: "users" as const, }, { title: "share_a_link_or_embed", description: "share_a_link_or_embed_description", icon: "link-2" as const, i18nOptions: { appName: APP_NAME, }, }, ]; function UsernameField({ username, setPremium, premium, setUsernameTaken, orgSlug, usernameTaken, disabled, ...props }: React.ComponentProps & { username: string; setPremium: (value: boolean) => void; premium: boolean; usernameTaken: boolean; orgSlug?: string; setUsernameTaken: (value: boolean) => void; }) { const { t } = useLocale(); const { register, formState } = useFormContext(); const debouncedUsername = useDebounce(username, 600); useEffect(() => { if (formState.isSubmitting || formState.isSubmitSuccessful) return; async function checkUsername() { // If the username can't be changed, there is no point in doing the username availability check if (disabled) return; if (!debouncedUsername) { setPremium(false); setUsernameTaken(false); return; } fetchUsername(debouncedUsername, orgSlug ?? null).then(({ data }) => { setPremium(data.premium); setUsernameTaken(!data.available); }); } checkUsername(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [debouncedUsername, disabled, orgSlug, formState.isSubmitting, formState.isSubmitSuccessful]); return (
{(!formState.isSubmitting || !formState.isSubmitted) && (
{usernameTaken ? (

{t("already_in_use_error")}

) : premium ? (

{t("premium_username", { price: getPremiumPlanPriceValue(), })}

) : null}
)}
); } function addOrUpdateQueryParam(url: string, key: string, value: string) { const separator = url.includes("?") ? "&" : "?"; const param = `${key}=${encodeURIComponent(value)}`; return `${url}${separator}${param}`; } export default function Signup({ prepopulateFormValues, token, orgSlug, isGoogleLoginEnabled, isSAMLLoginEnabled, orgAutoAcceptEmail, redirectUrl, emailVerificationEnabled, }: SignupProps) { const [premiumUsername, setPremiumUsername] = useState(false); const [usernameTaken, setUsernameTaken] = useState(false); const [isGoogleLoading, setIsGoogleLoading] = useState(false); const searchParams = useCompatSearchParams(); const telemetry = useTelemetry(); const { t, i18n } = useLocale(); const router = useRouter(); const formMethods = useForm({ resolver: zodResolver(signupSchema), defaultValues: prepopulateFormValues satisfies FormValues, mode: "onChange", }); const { register, watch, formState: { isSubmitting, errors, isSubmitSuccessful }, } = formMethods; useEffect(() => { if (redirectUrl) { localStorage.setItem("onBoardingRedirect", redirectUrl); } }, [redirectUrl]); const [COOKIE_CONSENT, setCOOKIE_CONSENT] = useState(false); function handleConsentChange(consent: boolean) { setCOOKIE_CONSENT(!consent); } const loadingSubmitState = isSubmitSuccessful || isSubmitting; const handleErrorsAndStripe = async (resp: Response) => { if (!resp.ok) { const err = await resp.json(); if (err.checkoutSessionId) { const stripe = await getStripe(); if (stripe) { console.log("Redirecting to stripe checkout"); const { error } = await stripe.redirectToCheckout({ sessionId: err.checkoutSessionId, }); console.warn(error.message); } } else { throw new Error(err.message); } } }; const isOrgInviteByLink = orgSlug && !prepopulateFormValues?.username; const isPlatformUser = redirectUrl?.includes("platform") && redirectUrl?.includes("new"); const signUp: SubmitHandler = async (_data) => { const { cfToken, ...data } = _data; await fetch("/api/auth/signup", { body: JSON.stringify({ ...data, language: i18n.language, token, }), headers: { "Content-Type": "application/json", "cf-access-token": cfToken ?? "invalid-token", }, method: "POST", }) .then(handleErrorsAndStripe) .then(async () => { if (process.env.NEXT_PUBLIC_GTM_ID) pushGTMEvent("create_account", { email: data.email, user: data.username, lang: data.language }); telemetry.event(telemetryEventTypes.signup, collectPageParameters()); const verifyOrGettingStarted = emailVerificationEnabled ? "auth/verify-email" : "getting-started"; const gettingStartedWithPlatform = "settings/platform/new"; const constructCallBackIfUrlPresent = () => { if (isOrgInviteByLink) { return `${WEBAPP_URL}/${searchParams.get("callbackUrl")}`; } return addOrUpdateQueryParam(`${WEBAPP_URL}/${searchParams.get("callbackUrl")}`, "from", "signup"); }; const constructCallBackIfUrlNotPresent = () => { if (!!isPlatformUser) { return `${WEBAPP_URL}/${gettingStartedWithPlatform}?from=signup`; } return `${WEBAPP_URL}/${verifyOrGettingStarted}?from=signup`; }; const constructCallBackUrl = () => { const callbackUrlSearchParams = searchParams?.get("callbackUrl"); return !!callbackUrlSearchParams ? constructCallBackIfUrlPresent() : constructCallBackIfUrlNotPresent(); }; const callBackUrl = constructCallBackUrl(); await signIn<"credentials">("credentials", { ...data, callbackUrl: callBackUrl, }); }) .catch((err) => { formMethods.setError("apiError", { message: err.message }); }); }; return ( <> {IS_CALCOM && COOKIE_CONSENT && process.env.NEXT_PUBLIC_GTM_ID ? ( <>