import { zodResolver } from "@hookform/resolvers/zod"; import type { GetServerSidePropsContext } from "next"; import { signIn } from "next-auth/react"; import { useSearchParams } from "next/navigation"; import type { CSSProperties } from "react"; import type { SubmitHandler } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form"; import { z } from "zod"; import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername"; import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains"; import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml"; import { useFlagMap } from "@calcom/features/flags/context/provider"; import { getFeatureFlagMap } from "@calcom/features/flags/server/utils"; import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import slugify from "@calcom/lib/slugify"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { signupSchema as apiSignupSchema } from "@calcom/prisma/zod-utils"; import type { inferSSRProps } from "@calcom/types/inferSSRProps"; import { Alert, Button, EmailField, HeadSeo, PasswordField, TextField } from "@calcom/ui"; import PageWrapper from "@components/PageWrapper"; import { IS_GOOGLE_LOGIN_ENABLED } from "../server/lib/constants"; import { ssrInit } from "../server/lib/ssr"; const signupSchema = apiSignupSchema.extend({ apiError: z.string().optional(), // Needed to display API errors doesnt get passed to the API }); type FormValues = z.infer; type SignupProps = inferSSRProps; const getSafeCallbackUrl = (url: string | null) => { if (!url) return null; let callbackUrl = url; if (/"\//.test(callbackUrl)) callbackUrl = callbackUrl.substring(1); // If not absolute URL, make it absolute if (!/^https?:\/\//.test(callbackUrl)) { callbackUrl = `${WEBAPP_URL}/${callbackUrl}`; } const safeCallbackUrl = getSafeRedirectUrl(callbackUrl); return safeCallbackUrl; }; export default function Signup({ prepopulateFormValues, token, orgSlug }: SignupProps) { const searchParams = useSearchParams(); const telemetry = useTelemetry(); const { t, i18n } = useLocale(); const flags = useFlagMap(); const methods = useForm({ mode: "onChange", resolver: zodResolver(signupSchema), defaultValues: prepopulateFormValues, }); const { register, formState: { errors, isSubmitting }, } = methods; const handleErrors = async (resp: Response) => { if (!resp.ok) { const err = await resp.json(); throw new Error(err.message); } }; const callbackUrl = getSafeCallbackUrl(searchParams.get("callbackUrl")); const signUp: SubmitHandler = async (data) => { await fetch("/api/auth/signup", { body: JSON.stringify({ ...data, language: i18n.language, token, }), headers: { "Content-Type": "application/json", }, method: "POST", }) .then(handleErrors) .then(async () => { telemetry.event(telemetryEventTypes.signup, collectPageParameters()); const verifyOrGettingStarted = flags["email-verification"] ? "auth/verify-email" : "getting-started"; await signIn<"credentials">("credentials", { ...data, callbackUrl: `${callbackUrl ? callbackUrl : `${WEBAPP_URL}/${verifyOrGettingStarted}`}?from=signup`, }); }) .catch((err) => { methods.setError("apiError", { message: err.message }); }); }; return ( <>

{t("create_your_account")}

{ event.preventDefault(); event.stopPropagation(); if (methods.formState?.errors?.apiError) { methods.clearErrors("apiError"); } methods.handleSubmit(signUp)(event); }} className="bg-default space-y-6"> {errors.apiError && }
{!token && ( )}
); } export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { const prisma = await import("@calcom/prisma").then((mod) => mod.default); const flags = await getFeatureFlagMap(prisma); const ssr = await ssrInit(ctx); const token = z.string().optional().parse(ctx.query.token); const props = { isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED, isSAMLLoginEnabled, trpcState: ssr.dehydrate(), prepopulateFormValues: undefined, }; if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" || flags["disable-signup"]) { return { notFound: true, }; } // no token given, treat as a normal signup without verification token if (!token) { return { props: JSON.parse(JSON.stringify(props)), }; } const verificationToken = await prisma.verificationToken.findUnique({ where: { token, }, include: { team: { select: { metadata: true, parentId: true, parent: { select: { slug: true, }, }, slug: true, }, }, }, }); if (!verificationToken || verificationToken.expires < new Date()) { return { notFound: true, }; } const existingUser = await prisma.user.findFirst({ where: { AND: [ { email: verificationToken?.identifier, }, { emailVerified: { not: null, }, }, ], }, }); if (existingUser) { return { redirect: { permanent: false, destination: "/auth/login?callbackUrl=" + `${WEBAPP_URL}/${ctx.query.callbackUrl}`, }, }; } const guessUsernameFromEmail = (email: string) => { const [username] = email.split("@"); return username; }; let username = guessUsernameFromEmail(verificationToken.identifier); const tokenTeam = { ...verificationToken?.team, metadata: teamMetadataSchema.parse(verificationToken?.team?.metadata), }; // Detect if the team is an org by either the metadata flag or if it has a parent team const isOrganization = tokenTeam.metadata?.isOrganization || tokenTeam?.parentId !== null; // If we are dealing with an org, the slug may come from the team itself or its parent const orgSlug = isOrganization ? tokenTeam.slug || tokenTeam.metadata?.requestedSlug || tokenTeam.parent?.slug : null; // Org context shouldn't check if a username is premium if (!IS_SELF_HOSTED && !isOrganization) { // Im not sure we actually hit this because of next redirects signup to website repo - but just in case this is pretty cool :) const { available, suggestion } = await checkPremiumUsername(username); username = available ? username : suggestion || username; } return { props: { ...props, token, prepopulateFormValues: { email: verificationToken.identifier, username: slugify(username), }, orgSlug, }, }; }; Signup.isThemeSupported = false; Signup.PageWrapper = PageWrapper;