Removed v2 pages directory, moved all pages to root and deleted old v1 pages and middleware redirect. (#5044)

* Removed v2 pages directory, moved all pages to root and deleted old v1 pages and middleware redirect.

* Removed v2 from imports path.

Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
Jeroen Reumkens
2022-10-17 18:14:43 +05:30
committed by GitHub
co-authored by Alex van Andel
parent aee1b3b596
commit 0284ffc75f
51 changed files with 74 additions and 530 deletions
@@ -1,4 +1,4 @@
import { FormValues } from "pages/v2/event-types/[type]";
import { FormValues } from "pages/event-types/[type]";
import { Controller, useFormContext } from "react-hook-form";
import dayjs from "@calcom/dayjs";
@@ -1,7 +1,7 @@
import autoAnimate from "@formkit/auto-animate";
import { EventTypeCustomInput } from "@prisma/client/";
import Link from "next/link";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import short from "short-uuid";
@@ -1,4 +1,4 @@
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import React from "react";
import { useFormContext } from "react-hook-form";
@@ -1,6 +1,6 @@
import autoAnimate from "@formkit/auto-animate";
import * as RadioGroup from "@radix-ui/react-radio-group";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef, useState } from "react";
import { useFormContext, Controller, useWatch } from "react-hook-form";
@@ -1,4 +1,4 @@
import { EventTypeSetupInfered } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered } from "pages/event-types/[type]";
import { useState } from "react";
import getStripeAppData from "@calcom/lib/getStripeAppData";
@@ -1,7 +1,7 @@
import autoAnimate from "@formkit/auto-animate";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef, useState } from "react";
import { Controller, useForm, useFormContext } from "react-hook-form";
import { z } from "zod";
@@ -1,5 +1,5 @@
import { SchedulingType } from "@prisma/client/";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useMemo } from "react";
import { Controller, useFormContext } from "react-hook-form";
@@ -1,6 +1,6 @@
import { TFunction } from "next-i18next";
import { useRouter } from "next/router";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useMemo, useState } from "react";
import { Loader } from "react-feather";
import { UseFormReturn } from "react-hook-form";
@@ -1,4 +1,4 @@
import type { FormValues } from "pages/v2/event-types/[type]";
import type { FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { useFormContext } from "react-hook-form";
-33
View File
@@ -5,29 +5,6 @@ import { CONSOLE_URL, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
import { isIpInBanlist } from "@calcom/lib/getIP";
import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry";
const V2_WHITELIST = [
"/settings/admin",
"/settings/billing",
"/settings/developer/webhooks",
"/settings/developer/api-keys",
"/settings/my-account",
"/settings/security",
"/settings/teams",
"/availability",
"/bookings",
"/event-types",
"/workflows",
"/apps",
"/teams",
"/success",
"/auth/login",
];
// For pages
// - which has V1 versions being modified as V2
// - Add routing_forms to keep old links working
const V2_BLACKLIST = ["/apps/routing_forms/", "/apps/routing-forms/", "/apps/typeform/"];
const middleware: NextMiddleware = async (req) => {
const url = req.nextUrl;
@@ -52,16 +29,6 @@ const middleware: NextMiddleware = async (req) => {
return NextResponse.rewrite(url);
}
/** Display available V2 pages */
if (
!V2_BLACKLIST.some((p) => url.pathname.startsWith(p)) &&
V2_WHITELIST.some((p) => url.pathname.startsWith(p))
) {
// rewrite to the current subdomain under the pages/sites folder
url.pathname = `/v2${url.pathname}`;
return NextResponse.rewrite(url);
}
return NextResponse.next();
};
+9 -39
View File
@@ -10,11 +10,9 @@ import path from "path";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import prisma from "@calcom/prisma";
import useMediaQuery from "@lib/hooks/useMediaQuery";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import App from "@components/App";
import Slider from "@components/apps/Slider";
import App from "@components/v2/apps/App";
const components = {
a: ({ href = "", ...otherProps }: JSX.IntrinsicElements["a"]) => (
@@ -25,42 +23,11 @@ const components = {
img: ({ src = "", alt = "", placeholder, ...rest }: JSX.IntrinsicElements["img"]) => (
<Image src={src} alt={alt} {...rest} />
),
Slider: ({ items }: { items: string[] }) => {
const isTabletAndUp = useMediaQuery("(min-width: 960px)");
return (
<Slider<string>
items={items}
title="Screenshots"
options={{
perView: 1,
}}
renderItem={(item) =>
isTabletAndUp ? (
<Image
src={item}
alt=""
loading="eager"
layout="fixed"
objectFit="contain"
objectPosition="center center"
width={573}
height={382}
/>
) : (
<Image
src={item}
alt=""
layout="responsive"
objectFit="contain"
objectPosition="center center"
width={573}
height={382}
/>
)
}
/>
);
},
// @TODO: In v2 the slider isn't shown anymore. However, to ensure the v1 pages keep
// working, this component is still rendered in the MDX content. To skip them in the v2
// content we have to render null here. In v2 the gallery is shown by directly
// using the `items` property from the MDX's meta data.
Slider: () => <></>,
};
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
@@ -68,6 +35,8 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
<App
name={data.name}
isGlobal={data.isGlobal}
slug={data.slug}
variant={data.variant}
type={data.type}
logo={data.logo}
categories={data.categories ?? [data.category]}
@@ -80,6 +49,7 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
email={data.email}
licenseRequired={data.licenseRequired}
isProOnly={data.isProOnly}
images={source?.scope?.items as string[] | undefined}
// tos="https://zoom.us/terms"
// privacy="https://zoom.us/privacy"
body={<MDXRemote {...source} components={components} />}
+3 -8
View File
@@ -2,9 +2,8 @@ import { GetStaticPaths, InferGetStaticPropsType } from "next";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { AppSetupPage } from "@calcom/app-store/_pages/setup";
import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
import Loader from "@calcom/ui/Loader";
import { AppSetupPage } from "@calcom/app-store/_pages/v2/setup";
export default function SetupInformation(props: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter();
@@ -12,11 +11,7 @@ export default function SetupInformation(props: InferGetStaticPropsType<typeof g
const { status } = useSession();
if (status === "loading") {
return (
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-200">
<Loader />
</div>
);
return <div className="absolute z-50 flex h-screen w-full items-center bg-gray-200" />;
}
if (status === "unauthenticated") {
@@ -28,7 +23,7 @@ export default function SetupInformation(props: InferGetStaticPropsType<typeof g
});
}
return <AppSetupPage slug={slug} {...props} />;
return <AppSetupPage slug={`${slug}-V2`} {...props} />;
}
export const getStaticPaths: GetStaticPaths = async () => {
+53 -57
View File
@@ -5,15 +5,16 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { FaGoogle } from "react-icons/fa";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma";
import { Icon } from "@calcom/ui";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import { Icon } from "@calcom/ui/Icon";
import { EmailField, Form, PasswordField } from "@calcom/ui/form/fields";
import { Button, EmailField, Form, PasswordField } from "@calcom/ui/v2";
import SAMLLogin from "@calcom/ui/v2/modules/auth/SAMLLogin";
import { ErrorCode, getSession } from "@lib/auth";
import { WEBAPP_URL, WEBSITE_URL } from "@lib/config/constants";
@@ -21,9 +22,8 @@ import { hostedCal, isSAMLLoginEnabled, samlProductID, samlTenantID } from "@lib
import { inferSSRProps } from "@lib/types/inferSSRProps";
import AddToHomescreen from "@components/AddToHomescreen";
import SAMLLogin from "@components/auth/SAMLLogin";
import TwoFactor from "@components/auth/TwoFactor";
import AuthContainer from "@components/ui/AuthContainer";
import AuthContainer from "@components/v2/ui/AuthContainer";
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
import { ssrInit } from "@server/lib/ssr";
@@ -53,8 +53,7 @@ export default function Login({
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const errorMessages: { [key: string]: string } = {
[ErrorCode.RateLimitExceeded]: t("rate_limit_exceeded"),
[ErrorCode.SecondFactorRequired]: t("2fa_enabled_instructions"),
// [ErrorCode.SecondFactorRequired]: t("2fa_enabled_instructions"),
[ErrorCode.IncorrectPassword]: `${t("incorrect_password")} ${t("please_try_again")}`,
[ErrorCode.UserNotFound]: t("no_account_exists"),
[ErrorCode.IncorrectTwoFactorCode]: `${t("incorrect_2fa_code")} ${t("please_try_again")}`,
@@ -78,12 +77,9 @@ export default function Login({
callbackUrl = safeCallbackUrl || "";
const LoginFooter = (
<span>
{t("dont_have_an_account")}{" "}
<a href={`${WEBSITE_URL}/signup`} className="font-medium text-neutral-900">
{t("create_an_account")}
</a>
</span>
<a href={`${WEBSITE_URL}/signup`} className="text-brand-500 font-medium">
{t("dont_have_an_account")}
</a>
);
const TwoFactorFooter = (
@@ -104,11 +100,10 @@ export default function Login({
title={t("login")}
description={t("login")}
showLogo
heading={twoFactorRequired ? t("2fa_code") : t("sign_in_account")}
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
footerText={twoFactorRequired ? TwoFactorFooter : LoginFooter}>
<Form
form={form}
className="space-y-6"
handleSubmit={async (values) => {
setErrorMessage(null);
telemetry.event(telemetryEventTypes.login, collectPageParameters());
@@ -134,51 +129,52 @@ export default function Login({
{...form.register("csrfToken")}
/>
</div>
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
<EmailField
id="email"
label={t("email_address")}
defaultValue={router.query.email as string}
placeholder="john.doe@example.com"
required
{...form.register("email")}
/>
<div className="relative">
<div className="absolute right-0 -top-[2px]">
<Link href="/auth/forgot-password">
<a tabIndex={-1} className="text-primary-600 text-sm font-medium">
{t("forgot")}
</a>
</Link>
</div>
<PasswordField
id="password"
type="password"
autoComplete="current-password"
<div className="space-y-6">
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
<EmailField
id="email"
label={t("email_address")}
defaultValue={router.query.email as string}
placeholder="john.doe@example.com"
required
{...form.register("password")}
{...form.register("email")}
/>
<div className="relative">
<div className="absolute right-0 -top-[6px] z-10">
<Link href="/auth/forgot-password">
<a tabIndex={-1} className="text-sm font-medium text-gray-600">
{t("forgot")}
</a>
</Link>
</div>
<PasswordField
id="password"
autoComplete="current-password"
required
className="mb-0"
{...form.register("password")}
/>
</div>
</div>
</div>
{twoFactorRequired && <TwoFactor center />}
{twoFactorRequired && <TwoFactor center />}
{errorMessage && <Alert severity="error" title={errorMessage} />}
<div className="flex space-y-2">
<Button className="flex w-full justify-center" type="submit" disabled={isSubmitting}>
{errorMessage && <Alert severity="error" title={errorMessage} />}
<Button type="submit" color="primary" disabled={isSubmitting} className="w-full justify-center">
{twoFactorRequired ? t("submit") : t("sign_in")}
</Button>
</div>
</Form>
{!twoFactorRequired && (
<>
{isGoogleLoginEnabled && (
<div className="mt-5">
{(isGoogleLoginEnabled || isSAMLLoginEnabled) && <hr className="my-8" />}
<div className="space-y-3">
{isGoogleLoginEnabled && (
<Button
color="secondary"
className="flex w-full justify-center"
className="w-full justify-center"
data-testid="google"
StartIcon={FaGoogle}
onClick={async (e) => {
e.preventDefault();
// track Google logins. Without personal data/payload
@@ -187,17 +183,17 @@ export default function Login({
}}>
{t("signin_with_google")}
</Button>
</div>
)}
{isSAMLLoginEnabled && (
<SAMLLogin
email={form.getValues("email")}
samlTenantID={samlTenantID}
samlProductID={samlProductID}
hostedCal={hostedCal}
setErrorMessage={setErrorMessage}
/>
)}
)}
{isSAMLLoginEnabled && (
<SAMLLogin
email={form.getValues("email")}
samlTenantID={samlTenantID}
samlProductID={samlProductID}
hostedCal={hostedCal}
setErrorMessage={setErrorMessage}
/>
)}
</div>
</>
)}
</AuthContainer>
-107
View File
@@ -1,107 +0,0 @@
import fs from "fs";
import matter from "gray-matter";
import { GetStaticPaths, GetStaticPropsContext } from "next";
import { MDXRemote } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import Image from "next/image";
import Link from "next/link";
import path from "path";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import prisma from "@calcom/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import App from "@components/v2/apps/App";
const components = {
a: ({ href = "", ...otherProps }: JSX.IntrinsicElements["a"]) => (
<Link href={href}>
<a {...otherProps} />
</Link>
),
img: ({ src = "", alt = "", placeholder, ...rest }: JSX.IntrinsicElements["img"]) => (
<Image src={src} alt={alt} {...rest} />
),
// @TODO: In v2 the slider isn't shown anymore. However, to ensure the v1 pages keep
// working, this component is still rendered in the MDX content. To skip them in the v2
// content we have to render null here. In v2 the gallery is shown by directly
// using the `items` property from the MDX's meta data.
Slider: () => <></>,
};
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
return (
<App
name={data.name}
isGlobal={data.isGlobal}
slug={data.slug}
variant={data.variant}
type={data.type}
logo={data.logo}
categories={data.categories ?? [data.category]}
author={data.publisher}
feeType={data.feeType || "usage-based"}
price={data.price || 0}
commission={data.commission || 0}
docs={data.docsUrl}
website={data.url}
email={data.email}
licenseRequired={data.licenseRequired}
isProOnly={data.isProOnly}
images={source?.scope?.items as string[] | undefined}
// tos="https://zoom.us/terms"
// privacy="https://zoom.us/privacy"
body={<MDXRemote {...source} components={components} />}
/>
);
}
export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
const appStore = await prisma.app.findMany({ select: { slug: true } });
const paths = appStore.map(({ slug }) => ({ params: { slug } }));
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (typeof ctx.params?.slug !== "string") return { notFound: true };
const app = await prisma.app.findUnique({
where: { slug: ctx.params.slug },
});
if (!app) return { notFound: true };
const singleApp = await getAppWithMetadata(app);
if (!singleApp) return { notFound: true };
const appDirname = app.dirName;
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/README.mdx`);
const postFilePath = path.join(README_PATH);
let source = "";
try {
/* If the app doesn't have a README we fallback to the package description */
source = fs.readFileSync(postFilePath).toString();
} catch (error) {
console.log(`No README.mdx provided for: ${appDirname}`);
source = singleApp.description;
}
const { content, data } = matter(source);
const mdxSource = await serialize(content, { scope: data });
return {
props: {
source: mdxSource,
data: singleApp,
},
};
};
export default SingleAppPage;
-36
View File
@@ -1,36 +0,0 @@
import { GetStaticPaths, InferGetStaticPropsType } from "next";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
import { AppSetupPage } from "@calcom/app-store/_pages/v2/setup";
export default function SetupInformation(props: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter();
const slug = router.query.slug as string;
const { status } = useSession();
if (status === "loading") {
return <div className="absolute z-50 flex h-screen w-full items-center bg-gray-200" />;
}
if (status === "unauthenticated") {
router.replace({
pathname: "/auth/login",
query: {
callbackUrl: `/apps/${slug}/setup`,
},
});
}
return <AppSetupPage slug={`${slug}-V2`} {...props} />;
}
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: [],
fallback: "blocking",
};
};
export { getStaticProps };
-241
View File
@@ -1,241 +0,0 @@
import classNames from "classnames";
import { GetServerSidePropsContext } from "next";
import { getCsrfToken, signIn } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { FaGoogle } from "react-icons/fa";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma";
import { Icon } from "@calcom/ui";
import { Alert } from "@calcom/ui/Alert";
import { Button, EmailField, Form, PasswordField } from "@calcom/ui/v2";
import SAMLLogin from "@calcom/ui/v2/modules/auth/SAMLLogin";
import { ErrorCode, getSession } from "@lib/auth";
import { WEBAPP_URL, WEBSITE_URL } from "@lib/config/constants";
import { hostedCal, isSAMLLoginEnabled, samlProductID, samlTenantID } from "@lib/saml";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import AddToHomescreen from "@components/AddToHomescreen";
import TwoFactor from "@components/auth/TwoFactor";
import AuthContainer from "@components/v2/ui/AuthContainer";
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
import { ssrInit } from "@server/lib/ssr";
interface LoginValues {
email: string;
password: string;
totpCode: string;
csrfToken: string;
}
export default function Login({
csrfToken,
isGoogleLoginEnabled,
isSAMLLoginEnabled,
hostedCal,
samlTenantID,
samlProductID,
}: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
const router = useRouter();
const form = useForm<LoginValues>();
const { formState } = form;
const { isSubmitting } = formState;
const [twoFactorRequired, setTwoFactorRequired] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const errorMessages: { [key: string]: string } = {
// [ErrorCode.SecondFactorRequired]: t("2fa_enabled_instructions"),
[ErrorCode.IncorrectPassword]: `${t("incorrect_password")} ${t("please_try_again")}`,
[ErrorCode.UserNotFound]: t("no_account_exists"),
[ErrorCode.IncorrectTwoFactorCode]: `${t("incorrect_2fa_code")} ${t("please_try_again")}`,
[ErrorCode.InternalServerError]: `${t("something_went_wrong")} ${t("please_try_again_and_contact_us")}`,
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
};
const telemetry = useTelemetry();
let callbackUrl = typeof router.query?.callbackUrl === "string" ? router.query.callbackUrl : "";
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);
callbackUrl = safeCallbackUrl || "";
const LoginFooter = (
<a href={`${WEBSITE_URL}/signup`} className="text-brand-500 font-medium">
{t("dont_have_an_account")}
</a>
);
const TwoFactorFooter = (
<Button
onClick={() => {
setTwoFactorRequired(false);
form.setValue("totpCode", "");
}}
StartIcon={Icon.FiArrowLeft}
color="minimal">
{t("go_back")}
</Button>
);
return (
<>
<AuthContainer
title={t("login")}
description={t("login")}
showLogo
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
footerText={twoFactorRequired ? TwoFactorFooter : LoginFooter}>
<Form
form={form}
handleSubmit={async (values) => {
setErrorMessage(null);
telemetry.event(telemetryEventTypes.login, collectPageParameters());
const res = await signIn<"credentials">("credentials", {
...values,
callbackUrl,
redirect: false,
});
if (!res) setErrorMessage(errorMessages[ErrorCode.InternalServerError]);
// we're logged in! let's do a hard refresh to the desired url
else if (!res.error) router.push(callbackUrl);
// reveal two factor input if required
else if (res.error === ErrorCode.SecondFactorRequired) setTwoFactorRequired(true);
// fallback if error not found
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
}}
data-testid="login-form">
<div>
<input
defaultValue={csrfToken || undefined}
type="hidden"
hidden
{...form.register("csrfToken")}
/>
</div>
<div className="space-y-6">
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
<EmailField
id="email"
label={t("email_address")}
defaultValue={router.query.email as string}
placeholder="john.doe@example.com"
required
{...form.register("email")}
/>
<div className="relative">
<div className="absolute right-0 -top-[6px] z-10">
<Link href="/auth/forgot-password">
<a tabIndex={-1} className="text-sm font-medium text-gray-600">
{t("forgot")}
</a>
</Link>
</div>
<PasswordField
id="password"
autoComplete="current-password"
required
className="mb-0"
{...form.register("password")}
/>
</div>
</div>
{twoFactorRequired && <TwoFactor center />}
{errorMessage && <Alert severity="error" title={errorMessage} />}
<Button type="submit" color="primary" disabled={isSubmitting} className="w-full justify-center">
{twoFactorRequired ? t("submit") : t("sign_in")}
</Button>
</div>
</Form>
{!twoFactorRequired && (
<>
{(isGoogleLoginEnabled || isSAMLLoginEnabled) && <hr className="my-8" />}
<div className="space-y-3">
{isGoogleLoginEnabled && (
<Button
color="secondary"
className="w-full justify-center"
data-testid="google"
StartIcon={FaGoogle}
onClick={async (e) => {
e.preventDefault();
// track Google logins. Without personal data/payload
telemetry.event(telemetryEventTypes.googleLogin, collectPageParameters());
await signIn("google");
}}>
{t("signin_with_google")}
</Button>
)}
{isSAMLLoginEnabled && (
<SAMLLogin
email={form.getValues("email")}
samlTenantID={samlTenantID}
samlProductID={samlProductID}
hostedCal={hostedCal}
setErrorMessage={setErrorMessage}
/>
)}
</div>
</>
)}
</AuthContainer>
<AddToHomescreen />
</>
);
}
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req } = context;
const session = await getSession({ req });
const ssr = await ssrInit(context);
if (session) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
const userCount = await prisma.user.count();
if (userCount === 0) {
// Proceed to new onboarding to create first admin user
return {
redirect: {
destination: "/auth/setup",
permanent: false,
},
};
}
return {
props: {
csrfToken: await getCsrfToken(context),
trpcState: ssr.dehydrate(),
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
isSAMLLoginEnabled,
hostedCal,
samlTenantID,
samlProductID,
},
};
}