From f8dc7a115a6f24f7a6c3ce444e98f9037c8e13c6 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Thu, 19 Sep 2024 11:06:51 -0400 Subject: [PATCH] chore: App Router - remove references to `/pages` for remaining pages in `/auth`, `/insights`, `/d`, `signup` and add missing default `/future` page (#16589) * remove references to /pages for sso, setup, signin pages in /auth * remove references to pages for insights * remove references to pages for d * remove references to pages for signup * add page for /future index page * fix routing-forms * fix * add missing defaults * use getServerSessionForAppDir instead * fix apps/[slug]/[...pages] * fix metadata in apps/slug/pages * refactor * refactor * remove duplicate code for PageProps * remove references to pages for /reschedule * fix * fix routing forms * type fix * fix routing forms again * revert changes for app/slug/pages * revert * revert changes in yarn lock --------- Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> --- .../future/apps/[slug]/[...pages]/page.tsx | 13 +- apps/web/app/future/auth/setup/page.tsx | 7 +- apps/web/app/future/auth/signin/page.tsx | 9 +- .../app/future/auth/sso/[provider]/page.tsx | 9 +- apps/web/app/future/auth/sso/direct/page.tsx | 8 +- apps/web/app/future/booking/[uid]/page.tsx | 10 +- apps/web/app/future/d/[link]/[slug]/page.tsx | 18 +- apps/web/app/future/insights/page.tsx | 9 +- .../app/future/org/[orgSlug]/embed/page.tsx | 10 +- apps/web/app/future/org/[orgSlug]/page.tsx | 10 +- .../org/[orgSlug]/team/[slug]/[type]/page.tsx | 10 +- .../future/org/[orgSlug]/team/[slug]/page.tsx | 10 +- apps/web/app/future/page.tsx | 13 + .../future/reschedule/[uid]/embed/page.tsx | 7 +- apps/web/app/future/reschedule/[uid]/page.tsx | 2 +- .../future/routing-forms/[...pages]/page.tsx | 19 +- apps/web/app/future/routing-forms/page.tsx | 27 +- apps/web/app/future/signup/page.tsx | 6 +- apps/web/app/layoutHOC.tsx | 1 + .../[slug]/[...pages]/getServerSideProps.ts | 2 +- .../reschedule/[uid]/getServerSideProps.ts | 180 +++++ .../apps/[slug]/[...pages]/pages-view.tsx | 5 +- apps/web/modules/auth/setup-view.tsx | 147 ++++ apps/web/modules/auth/signin-view.tsx | 29 + apps/web/modules/auth/sso/direct-view.tsx | 40 ++ apps/web/modules/auth/sso/provider-view.tsx | 41 ++ apps/web/modules/d/[link]/d-type-view.tsx | 43 ++ apps/web/modules/insights/insights-view.tsx | 120 ++++ apps/web/modules/signup-view.tsx | 661 +++++++++++++++++ apps/web/pages/auth/setup/index.tsx | 144 +--- apps/web/pages/auth/signin.tsx | 30 +- apps/web/pages/auth/sso/[provider].tsx | 45 +- apps/web/pages/auth/sso/direct.tsx | 44 +- apps/web/pages/d/[link]/[slug].tsx | 45 +- apps/web/pages/insights/index.tsx | 120 +--- apps/web/pages/reschedule/[uid].tsx | 181 +---- apps/web/pages/reschedule/[uid]/embed.tsx | 2 - apps/web/pages/signup.tsx | 665 +----------------- .../pages/layout-handler/[...appPages].tsx | 2 +- yarn.lock | 2 +- 40 files changed, 1388 insertions(+), 1358 deletions(-) create mode 100644 apps/web/app/future/page.tsx create mode 100644 apps/web/lib/reschedule/[uid]/getServerSideProps.ts create mode 100644 apps/web/modules/auth/setup-view.tsx create mode 100644 apps/web/modules/auth/signin-view.tsx create mode 100644 apps/web/modules/auth/sso/direct-view.tsx create mode 100644 apps/web/modules/auth/sso/provider-view.tsx create mode 100644 apps/web/modules/d/[link]/d-type-view.tsx create mode 100644 apps/web/modules/insights/insights-view.tsx create mode 100644 apps/web/modules/signup-view.tsx diff --git a/apps/web/app/future/apps/[slug]/[...pages]/page.tsx b/apps/web/app/future/apps/[slug]/[...pages]/page.tsx index ad854995bd..e67645d9fb 100644 --- a/apps/web/app/future/apps/[slug]/[...pages]/page.tsx +++ b/apps/web/app/future/apps/[slug]/[...pages]/page.tsx @@ -1,9 +1,8 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; -import type { SearchParams } from "app/_types"; +import type { PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import type { GetServerSidePropsResult } from "next"; -import type { Params } from "next/dist/shared/lib/router/utils/route-matcher"; import { cookies, headers } from "next/headers"; import { notFound } from "next/navigation"; import z from "zod"; @@ -18,13 +17,7 @@ const paramsSchema = z.object({ pages: z.array(z.string()), }); -export const generateMetadata = async ({ - params, - searchParams, -}: { - params: Params; - searchParams: SearchParams; -}) => { +export const generateMetadata = async ({ params, searchParams }: PageProps) => { const p = paramsSchema.safeParse(params); if (!p.success) { @@ -34,7 +27,7 @@ export const generateMetadata = async ({ const legacyContext = buildLegacyCtx(headers(), cookies(), params, searchParams); const data = await getData(legacyContext); const form = "form" in data ? (data.form as { name?: string; description?: string }) : null; - const formName = form?.name ?? "Forms"; + const formName = form?.name ?? "Routing Forms"; const formDescription = form?.description ?? ""; return await _generateMetadata( diff --git a/apps/web/app/future/auth/setup/page.tsx b/apps/web/app/future/auth/setup/page.tsx index b33b9693ab..5f4d478e45 100644 --- a/apps/web/app/future/auth/setup/page.tsx +++ b/apps/web/app/future/auth/setup/page.tsx @@ -1,12 +1,13 @@ -import Setup from "@pages/auth/setup"; import { withAppDirSsr } from "app/WithAppDirSsr"; import { WithLayout } from "app/layoutHOC"; -import type { InferGetServerSidePropsType } from "next"; import { getServerSideProps } from "@server/lib/setup/getServerSideProps"; +import Setup from "~/auth/setup-view"; +import type { PageProps } from "~/auth/setup-view"; + export default WithLayout({ getLayout: null, Page: Setup, - getData: withAppDirSsr>(getServerSideProps), + getData: withAppDirSsr(getServerSideProps), })<"P">; diff --git a/apps/web/app/future/auth/signin/page.tsx b/apps/web/app/future/auth/signin/page.tsx index bb19d2bc21..18354c4f9c 100644 --- a/apps/web/app/future/auth/signin/page.tsx +++ b/apps/web/app/future/auth/signin/page.tsx @@ -1,13 +1,14 @@ -import signin from "@pages/auth/signin"; import { withAppDirSsr } from "app/WithAppDirSsr"; import { WithLayout } from "app/layoutHOC"; -import type { InferGetServerSidePropsType } from "next"; import { getServerSideProps } from "@server/lib/auth/signin/getServerSideProps"; +import SignIn from "~/auth/signin-view"; +import type { PageProps } from "~/auth/signin-view"; + export default WithLayout({ getLayout: null, - Page: signin, + Page: SignIn, // @ts-expect-error TODO: fix this - getData: withAppDirSsr>(getServerSideProps), + getData: withAppDirSsr(getServerSideProps), })<"P">; diff --git a/apps/web/app/future/auth/sso/[provider]/page.tsx b/apps/web/app/future/auth/sso/[provider]/page.tsx index 2a59c87a8a..efac8c1f18 100644 --- a/apps/web/app/future/auth/sso/[provider]/page.tsx +++ b/apps/web/app/future/auth/sso/[provider]/page.tsx @@ -1,12 +1,13 @@ -import Provider from "@pages/auth/sso/[provider]"; import { withAppDirSsr } from "app/WithAppDirSsr"; import { WithLayout } from "app/layoutHOC"; -import type { InferGetServerSidePropsType } from "next"; import { getServerSideProps } from "@server/lib/auth/sso/[provider]/getServerSideProps"; +import type { SSOProviderPageProps } from "~/auth/sso/provider-view"; +import SSOProviderView from "~/auth/sso/provider-view"; + export default WithLayout({ getLayout: null, - Page: Provider, - getData: withAppDirSsr>(getServerSideProps), + Page: SSOProviderView, + getData: withAppDirSsr(getServerSideProps), })<"P">; diff --git a/apps/web/app/future/auth/sso/direct/page.tsx b/apps/web/app/future/auth/sso/direct/page.tsx index 0f69f324fe..a920c02ba3 100644 --- a/apps/web/app/future/auth/sso/direct/page.tsx +++ b/apps/web/app/future/auth/sso/direct/page.tsx @@ -1,11 +1,13 @@ -import DirectSSOLogin from "@pages/auth/sso/direct"; import { withAppDirSsr } from "app/WithAppDirSsr"; import { WithLayout } from "app/layoutHOC"; import { getServerSideProps } from "@server/lib/auth/sso/direct/getServerSideProps"; +import type { SSODirectPageProps } from "~/auth/sso/direct-view"; +import SSODirectView from "~/auth/sso/direct-view"; + export default WithLayout({ getLayout: null, - Page: DirectSSOLogin, - getData: withAppDirSsr(getServerSideProps), + Page: SSODirectView, + getData: withAppDirSsr(getServerSideProps), })<"P">; diff --git a/apps/web/app/future/booking/[uid]/page.tsx b/apps/web/app/future/booking/[uid]/page.tsx index 238dd365ee..e192d88b61 100644 --- a/apps/web/app/future/booking/[uid]/page.tsx +++ b/apps/web/app/future/booking/[uid]/page.tsx @@ -1,5 +1,5 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; -import type { Params, SearchParams } from "app/_types"; +import type { PageProps as _PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { cookies, headers } from "next/headers"; @@ -11,13 +11,7 @@ import { buildLegacyCtx } from "@lib/buildLegacyCtx"; import OldPage from "~/bookings/views/bookings-single-view"; import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps"; -export const generateMetadata = async ({ - params, - searchParams, -}: { - params: Params; - searchParams: SearchParams; -}) => { +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { const { bookingInfo, eventType, recurringBookings } = await getData( buildLegacyCtx(headers(), cookies(), params, searchParams) ); diff --git a/apps/web/app/future/d/[link]/[slug]/page.tsx b/apps/web/app/future/d/[link]/[slug]/page.tsx index 03c250363f..0d194c6853 100644 --- a/apps/web/app/future/d/[link]/[slug]/page.tsx +++ b/apps/web/app/future/d/[link]/[slug]/page.tsx @@ -1,6 +1,5 @@ -import LegacyPage from "@pages/d/[link]/[slug]"; import { withAppDirSsr } from "app/WithAppDirSsr"; -import type { Params, SearchParams } from "app/_types"; +import type { PageProps as _PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { cookies, headers } from "next/headers"; @@ -10,14 +9,11 @@ import { EventRepository } from "@calcom/lib/server/repository/event"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; import { getServerSideProps } from "@lib/d/[link]/[slug]/getServerSideProps"; +import { type PageProps } from "@lib/d/[link]/[slug]/getServerSideProps"; -export const generateMetadata = async ({ - params, - searchParams, -}: { - params: Params; - searchParams: SearchParams; -}) => { +import Type from "~/d/[link]/d-type-view"; + +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); const pageProps = await getData(legacyCtx); @@ -42,5 +38,5 @@ export const generateMetadata = async ({ ); }; -const getData = withAppDirSsr(getServerSideProps); -export default WithLayout({ getLayout: null, Page: LegacyPage, getData })<"P">; +const getData = withAppDirSsr(getServerSideProps); +export default WithLayout({ getLayout: null, Page: Type, getData })<"P">; diff --git a/apps/web/app/future/insights/page.tsx b/apps/web/app/future/insights/page.tsx index 24f94e4f8e..1260490c07 100644 --- a/apps/web/app/future/insights/page.tsx +++ b/apps/web/app/future/insights/page.tsx @@ -1,10 +1,11 @@ -import LegacyPage from "@pages/insights/index"; import { withAppDirSsr } from "app/WithAppDirSsr"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { getServerSideProps } from "@lib/insights/getServerSideProps"; -import { type inferSSRProps } from "@lib/types/inferSSRProps"; + +import type { PageProps } from "~/insights/insights-view"; +import InsightsPage from "~/insights/insights-view"; export const generateMetadata = async () => await _generateMetadata( @@ -12,6 +13,6 @@ export const generateMetadata = async () => (t) => t("insights_subtitle") ); -const getData = withAppDirSsr>(getServerSideProps); +const getData = withAppDirSsr(getServerSideProps); -export default WithLayout({ getLayout: null, getData, Page: LegacyPage }); +export default WithLayout({ getLayout: null, getData, Page: InsightsPage }); diff --git a/apps/web/app/future/org/[orgSlug]/embed/page.tsx b/apps/web/app/future/org/[orgSlug]/embed/page.tsx index 499173cf98..111865eeaf 100644 --- a/apps/web/app/future/org/[orgSlug]/embed/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/embed/page.tsx @@ -1,6 +1,6 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; import withEmbedSsrAppDir from "app/WithEmbedSSR"; -import type { Params, SearchParams } from "app/_types"; +import type { PageProps as _PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { cookies, headers } from "next/headers"; @@ -13,13 +13,7 @@ import TeamPage from "~/team/team-view"; const getData = withAppDirSsr(getServerSideProps); -export const generateMetadata = async ({ - params, - searchParams, -}: { - params: Params; - searchParams: SearchParams; -}) => { +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); const teamName = props.team.name || "Nameless Team"; diff --git a/apps/web/app/future/org/[orgSlug]/page.tsx b/apps/web/app/future/org/[orgSlug]/page.tsx index d6572bbb91..b84f78b967 100644 --- a/apps/web/app/future/org/[orgSlug]/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/page.tsx @@ -1,5 +1,5 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; -import type { Params, SearchParams } from "app/_types"; +import type { PageProps as _PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { cookies, headers } from "next/headers"; @@ -10,13 +10,7 @@ import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; import type { PageProps } from "~/team/team-view"; import TeamPage from "~/team/team-view"; -export const generateMetadata = async ({ - params, - searchParams, -}: { - params: Params; - searchParams: SearchParams; -}) => { +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); const teamName = props.team.name || "Nameless Team"; diff --git a/apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx b/apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx index 22cb127b72..ea51f5cd9b 100644 --- a/apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx @@ -1,5 +1,5 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; -import type { Params, SearchParams } from "app/_types"; +import type { PageProps as _PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { cookies, headers } from "next/headers"; @@ -13,13 +13,7 @@ import { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps"; import type { PageProps } from "~/team/type-view"; import TypePage from "~/team/type-view"; -export const generateMetadata = async ({ - params, - searchParams, -}: { - params: Params; - searchParams: SearchParams; -}) => { +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); const props = await getData(legacyCtx); const { user: username, slug: eventSlug, booking } = props; diff --git a/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx b/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx index d6572bbb91..b84f78b967 100644 --- a/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx @@ -1,5 +1,5 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; -import type { Params, SearchParams } from "app/_types"; +import type { PageProps as _PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { cookies, headers } from "next/headers"; @@ -10,13 +10,7 @@ import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; import type { PageProps } from "~/team/team-view"; import TeamPage from "~/team/team-view"; -export const generateMetadata = async ({ - params, - searchParams, -}: { - params: Params; - searchParams: SearchParams; -}) => { +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); const teamName = props.team.name || "Nameless Team"; diff --git a/apps/web/app/future/page.tsx b/apps/web/app/future/page.tsx new file mode 100644 index 0000000000..f82ff90851 --- /dev/null +++ b/apps/web/app/future/page.tsx @@ -0,0 +1,13 @@ +import { redirect } from "next/navigation"; + +import { getServerSessionForAppDir } from "@calcom/features/auth/lib/get-server-session-for-app-dir"; + +const RedirectPage = async () => { + const session = await getServerSessionForAppDir(); + if (!session?.user?.id) { + redirect("/auth/login"); + } + redirect("/event-types"); +}; + +export default RedirectPage; diff --git a/apps/web/app/future/reschedule/[uid]/embed/page.tsx b/apps/web/app/future/reschedule/[uid]/embed/page.tsx index 4cc22aabf2..52f7f7e1d9 100644 --- a/apps/web/app/future/reschedule/[uid]/embed/page.tsx +++ b/apps/web/app/future/reschedule/[uid]/embed/page.tsx @@ -1,14 +1,15 @@ -import { getServerSideProps } from "@pages/reschedule/[uid]"; -import { withAppDirSsr } from "app/WithAppDirSsr"; +import { getServerSideProps as _getServerSideProps } from "@pages/reschedule/[uid]"; import type { PageProps } from "app/_types"; import { cookies, headers } from "next/headers"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; import withEmbedSsr from "@lib/withEmbedSsr"; +const getData = withEmbedSsr(_getServerSideProps); + const Page = async ({ params, searchParams }: PageProps) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); - await withAppDirSsr(withEmbedSsr(getServerSideProps))(legacyCtx); + await getData(legacyCtx); return null; }; diff --git a/apps/web/app/future/reschedule/[uid]/page.tsx b/apps/web/app/future/reschedule/[uid]/page.tsx index 921369891f..4f629a3ba0 100644 --- a/apps/web/app/future/reschedule/[uid]/page.tsx +++ b/apps/web/app/future/reschedule/[uid]/page.tsx @@ -1,10 +1,10 @@ -import { getServerSideProps } from "@pages/reschedule/[uid]"; import { withAppDirSsr } from "app/WithAppDirSsr"; import type { PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { headers, cookies } from "next/headers"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import { getServerSideProps } from "@lib/reschedule/[uid]/getServerSideProps"; export const generateMetadata = async () => await _generateMetadata( diff --git a/apps/web/app/future/routing-forms/[...pages]/page.tsx b/apps/web/app/future/routing-forms/[...pages]/page.tsx index 3ee4fbd90d..4ecc5a1aa0 100644 --- a/apps/web/app/future/routing-forms/[...pages]/page.tsx +++ b/apps/web/app/future/routing-forms/[...pages]/page.tsx @@ -1,12 +1,19 @@ +import type { PageProps } from "app/_types"; import { redirect } from "next/navigation"; +import z from "zod"; -const getPageProps = () => { - return redirect(`/apps/routing-forms/forms`); -}; -const Page = () => { - getPageProps(); +const paramsSchema = z + .object({ + pages: z.array(z.string()), + }) + .catch({ + pages: [], + }); - return null; +const Page = ({ params, searchParams }: PageProps) => { + const { pages } = paramsSchema.parse({ ...params, ...searchParams }); + + redirect(`/apps/routing-forms/${pages.length ? pages.join("/") : ""}`); }; export default Page; diff --git a/apps/web/app/future/routing-forms/page.tsx b/apps/web/app/future/routing-forms/page.tsx index f904f3eaf4..ba222ba476 100644 --- a/apps/web/app/future/routing-forms/page.tsx +++ b/apps/web/app/future/routing-forms/page.tsx @@ -1,30 +1,7 @@ -import type { PageProps } from "app/_types"; -import { type GetServerSidePropsContext } from "next"; -import { cookies, headers } from "next/headers"; import { redirect } from "next/navigation"; -import z from "zod"; -import { buildLegacyCtx } from "@lib/buildLegacyCtx"; - -const paramsSchema = z - .object({ - pages: z.array(z.string()), - }) - .catch({ - pages: [], - }); - -const getPageProps = async (context: GetServerSidePropsContext) => { - const { pages } = paramsSchema.parse(context.params); - - return redirect(`/apps/routing-forms/${pages.length ? pages.join("/") : ""}`); -}; - -const Page = async ({ params, searchParams }: PageProps) => { - const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); - await getPageProps(legacyCtx); - - return null; +const Page = () => { + redirect("/apps/routing-forms/forms"); }; export default Page; diff --git a/apps/web/app/future/signup/page.tsx b/apps/web/app/future/signup/page.tsx index d24988d1d8..ff1a9cd80a 100644 --- a/apps/web/app/future/signup/page.tsx +++ b/apps/web/app/future/signup/page.tsx @@ -1,10 +1,12 @@ -import LegacyPage, { type SignupProps } from "@pages/signup"; import { withAppDirSsr } from "app/WithAppDirSsr"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { getServerSideProps } from "@lib/signup/getServerSideProps"; +import type { SignupProps } from "~/signup-view"; +import Signup from "~/signup-view"; + export const generateMetadata = async () => await _generateMetadata( (t) => t("sign_up"), @@ -14,7 +16,7 @@ export const generateMetadata = async () => const getData = withAppDirSsr(getServerSideProps); export default WithLayout({ - Page: LegacyPage, + Page: Signup, getLayout: null, getData, })<"P">; diff --git a/apps/web/app/layoutHOC.tsx b/apps/web/app/layoutHOC.tsx index cd7070b270..8f71154e31 100644 --- a/apps/web/app/layoutHOC.tsx +++ b/apps/web/app/layoutHOC.tsx @@ -51,6 +51,7 @@ export function WithLayout>({ requiresLicense={requiresLicense || !!(Page && "requiresLicense" in Page && Page.requiresLicense)} nonce={nonce} themeBasis={null} + isThemeSupported={!!(Page && "isThemeSupported" in Page && Page.isThemeSupported)} isBookingPage={isBookingPage || !!(Page && "isBookingPage" in Page && Page.isBookingPage)} {...props}> {pageWithServerLayout} diff --git a/apps/web/lib/apps/[slug]/[...pages]/getServerSideProps.ts b/apps/web/lib/apps/[slug]/[...pages]/getServerSideProps.ts index 77591446c8..5cd62aade0 100644 --- a/apps/web/lib/apps/[slug]/[...pages]/getServerSideProps.ts +++ b/apps/web/lib/apps/[slug]/[...pages]/getServerSideProps.ts @@ -13,7 +13,7 @@ import type { AppProps } from "@lib/app-providers"; import { ssrInit } from "@server/lib/ssr"; type AppPageType = { - getServerSideProps: AppGetServerSideProps; + getServerSideProps?: AppGetServerSideProps; // A component than can accept any properties // eslint-disable-next-line @typescript-eslint/no-explicit-any default: ((props: any) => JSX.Element) & diff --git a/apps/web/lib/reschedule/[uid]/getServerSideProps.ts b/apps/web/lib/reschedule/[uid]/getServerSideProps.ts new file mode 100644 index 0000000000..90fa034322 --- /dev/null +++ b/apps/web/lib/reschedule/[uid]/getServerSideProps.ts @@ -0,0 +1,180 @@ +// page can be a server component +import type { GetServerSidePropsContext } from "next"; +import { URLSearchParams } from "url"; +import { z } from "zod"; + +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { buildEventUrlFromBooking } from "@calcom/lib/bookings/buildEventUrlFromBooking"; +import { getDefaultEvent } from "@calcom/lib/defaultEvents"; +import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat"; +import { UserRepository } from "@calcom/lib/server/repository/user"; +import prisma, { bookingMinimalSelect } from "@calcom/prisma"; +import { BookingStatus } from "@calcom/prisma/client"; + +const querySchema = z.object({ + uid: z.string(), + seatReferenceUid: z.string().optional(), + rescheduledBy: z.string().optional(), + allowRescheduleForCancelledBooking: z + .string() + .transform((value) => value === "true") + .optional(), +}); + +export async function getServerSideProps(context: GetServerSidePropsContext) { + const session = await getServerSession(context); + + const { + uid: bookingUid, + seatReferenceUid, + rescheduledBy, + /** + * This is for the case of request-reschedule where the booking is cancelled + */ + allowRescheduleForCancelledBooking, + } = querySchema.parse(context.query); + + const coepFlag = context.query["flag.coep"]; + const { uid, seatReferenceUid: maybeSeatReferenceUid } = await maybeGetBookingUidFromSeat( + prisma, + bookingUid + ); + + const booking = await prisma.booking.findUnique({ + where: { + uid, + }, + select: { + ...bookingMinimalSelect, + eventType: { + select: { + users: { + select: { + username: true, + }, + }, + slug: true, + team: { + select: { + parentId: true, + slug: true, + }, + }, + seatsPerTimeSlot: true, + userId: true, + owner: { + select: { + id: true, + }, + }, + hosts: { + select: { + user: { + select: { + id: true, + }, + }, + }, + }, + }, + }, + dynamicEventSlugRef: true, + dynamicGroupSlugRef: true, + user: true, + status: true, + }, + }); + const dynamicEventSlugRef = booking?.dynamicEventSlugRef || ""; + + if (!booking) { + return { + notFound: true, + } as const; + } + + // If booking is already CANCELLED or REJECTED, we can't reschedule this booking. Take the user to the booking page which would show it's correct status and other details. + // A booking that has been rescheduled to a new booking will also have a status of CANCELLED + if ( + !allowRescheduleForCancelledBooking && + (booking.status === BookingStatus.CANCELLED || booking.status === BookingStatus.REJECTED) + ) { + return { + redirect: { + destination: `/booking/${uid}`, + permanent: false, + }, + }; + } + + if (!booking?.eventType && !booking?.dynamicEventSlugRef) { + // TODO: Show something in UI to let user know that this booking is not rescheduleable + return { + notFound: true, + } as { + notFound: true; + }; + } + + // if booking event type is for a seated event and no seat reference uid is provided, throw not found + if (booking?.eventType?.seatsPerTimeSlot && !maybeSeatReferenceUid) { + const userId = session?.user?.id; + + if (!userId && !seatReferenceUid) { + return { + redirect: { + destination: `/auth/login?callbackUrl=/reschedule/${bookingUid}`, + permanent: false, + }, + }; + } + const userIsHost = booking?.eventType.hosts.find((host) => { + if (host.user.id === userId) return true; + }); + + const userIsOwnerOfEventType = booking?.eventType.owner?.id === userId; + + if (!userIsHost && !userIsOwnerOfEventType) { + return { + notFound: true, + } as { + notFound: true; + }; + } + } + + const eventType = booking.eventType ? booking.eventType : getDefaultEvent(dynamicEventSlugRef); + + const enrichedBookingUser = booking.user + ? await UserRepository.enrichUserWithItsProfile({ user: booking.user }) + : null; + + const eventUrl = await buildEventUrlFromBooking({ + eventType, + dynamicGroupSlugRef: booking.dynamicGroupSlugRef ?? null, + profileEnrichedBookingUser: enrichedBookingUser, + }); + + const destinationUrlSearchParams = new URLSearchParams(); + + destinationUrlSearchParams.set("rescheduleUid", seatReferenceUid || bookingUid); + + // TODO: I think we should just forward all the query params here including coep flag + if (coepFlag) { + destinationUrlSearchParams.set("flag.coep", coepFlag as string); + } + + const currentUserEmail = rescheduledBy ?? session?.user?.email; + + if (currentUserEmail) { + destinationUrlSearchParams.set("rescheduledBy", currentUserEmail); + } + + return { + redirect: { + destination: `${eventUrl}?${destinationUrlSearchParams.toString()}${ + eventType.seatsPerTimeSlot ? "&bookingUid=null" : "" + }`, + permanent: false, + }, + }; +} diff --git a/apps/web/modules/apps/[slug]/[...pages]/pages-view.tsx b/apps/web/modules/apps/[slug]/[...pages]/pages-view.tsx index b6e8c455c6..1a1ca5d06f 100644 --- a/apps/web/modules/apps/[slug]/[...pages]/pages-view.tsx +++ b/apps/web/modules/apps/[slug]/[...pages]/pages-view.tsx @@ -34,7 +34,10 @@ const AppsRouting = { }; function getRoute(appName: string, pages: string[]) { - const routingConfig = AppsRouting[appName as keyof typeof AppsRouting] as Record; + const routingConfig = AppsRouting[appName as keyof typeof AppsRouting] as unknown as Record< + string, + AppPageType + >; if (!routingConfig) { return { diff --git a/apps/web/modules/auth/setup-view.tsx b/apps/web/modules/auth/setup-view.tsx new file mode 100644 index 0000000000..7eed32f224 --- /dev/null +++ b/apps/web/modules/auth/setup-view.tsx @@ -0,0 +1,147 @@ +"use client"; + +import { usePathname, useRouter } from "next/navigation"; +import { useState } from "react"; + +import AdminAppsList from "@calcom/features/apps/AdminAppsList"; +import { APP_NAME } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import type { inferSSRProps } from "@calcom/types/inferSSRProps"; +import { WizardForm } from "@calcom/ui"; + +import { AdminUserContainer as AdminUser } from "@components/setup/AdminUser"; +import ChooseLicense from "@components/setup/ChooseLicense"; +import EnterpriseLicense from "@components/setup/EnterpriseLicense"; + +import type { getServerSideProps } from "@server/lib/setup/getServerSideProps"; + +function useSetStep() { + const router = useRouter(); + const searchParams = useCompatSearchParams(); + const pathname = usePathname(); + const setStep = (newStep = 1) => { + const _searchParams = new URLSearchParams(searchParams ?? undefined); + _searchParams.set("step", newStep.toString()); + router.replace(`${pathname}?${_searchParams.toString()}`); + }; + return setStep; +} + +export type PageProps = inferSSRProps; +export function Setup(props: PageProps) { + const { t } = useLocale(); + const router = useRouter(); + const [value, setValue] = useState(props.isFreeLicense ? "FREE" : "EE"); + const isFreeLicense = value === "FREE"; + const [isEnabledEE, setIsEnabledEE] = useState(!props.isFreeLicense); + const setStep = useSetStep(); + + const steps: React.ComponentProps["steps"] = [ + { + title: t("administrator_user"), + description: t("lets_create_first_administrator_user"), + content: (setIsPending) => ( + { + setIsPending(true); + }} + onSuccess={() => { + setStep(2); + }} + onError={() => { + setIsPending(false); + }} + userCount={props.userCount} + /> + ), + }, + { + title: t("choose_a_license"), + description: t("choose_license_description"), + content: (setIsPending) => { + return ( + { + setIsPending(true); + setStep(3); + }} + /> + ); + }, + }, + ]; + + if (!isFreeLicense) { + steps.push({ + title: t("step_enterprise_license"), + description: t("step_enterprise_license_description"), + content: (setIsPending) => { + const currentStep = 3; + return ( + { + setIsPending(true); + }} + onSuccess={() => { + setStep(currentStep + 1); + }} + onSuccessValidate={() => { + setIsEnabledEE(true); + }} + /> + ); + }, + isEnabled: isEnabledEE, + }); + } + + steps.push({ + title: t("enable_apps"), + description: t("enable_apps_description", { appName: APP_NAME }), + contentClassname: "!pb-0 mb-[-1px]", + content: (setIsPending) => { + const currentStep = isFreeLicense ? 3 : 4; + return ( + { + setIsPending(true); + router.replace("/"); + }} + /> + ); + }, + }); + + return ( +
+ t("current_step_of_total", { currentStep, maxSteps })} + /> +
+ ); +} + +Setup.isThemeSupported = false; + +export default Setup; diff --git a/apps/web/modules/auth/signin-view.tsx b/apps/web/modules/auth/signin-view.tsx new file mode 100644 index 0000000000..eb51693790 --- /dev/null +++ b/apps/web/modules/auth/signin-view.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { signIn } from "next-auth/react"; + +import type { inferSSRProps } from "@calcom/types/inferSSRProps"; +import { Button } from "@calcom/ui"; + +import type { getServerSideProps } from "@server/lib/auth/signin/getServerSideProps"; + +export type PageProps = inferSSRProps; +function Signin({ providers }: PageProps) { + if (!providers) { + return null; + } + + return ( +
+ {Object.values(providers).map((provider) => { + return ( +
+ +
+ ); + })} +
+ ); +} + +export default Signin; diff --git a/apps/web/modules/auth/sso/direct-view.tsx b/apps/web/modules/auth/sso/direct-view.tsx new file mode 100644 index 0000000000..f6c7bc8bf5 --- /dev/null +++ b/apps/web/modules/auth/sso/direct-view.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { signIn } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; + +import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants"; + +import type { inferSSRProps } from "@lib/types/inferSSRProps"; + +import type { getServerSideProps } from "@server/lib/auth/sso/direct/getServerSideProps"; + +// This page is used to initiate the SAML authentication flow by redirecting to the SAML provider. +// Accessible only on self-hosted Cal.com instances. + +export type SSODirectPageProps = inferSSRProps; +export default function Page({ samlTenantID, samlProductID }: SSODirectPageProps) { + const router = useRouter(); + + useEffect(() => { + if (HOSTED_CAL_FEATURES) { + router.push("/auth/login"); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + // Initiate SAML authentication flow + signIn( + "saml", + { + callbackUrl: "/", + }, + { tenant: samlTenantID, product: samlProductID } + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return null; +} diff --git a/apps/web/modules/auth/sso/provider-view.tsx b/apps/web/modules/auth/sso/provider-view.tsx new file mode 100644 index 0000000000..bc9d5a5964 --- /dev/null +++ b/apps/web/modules/auth/sso/provider-view.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { signIn } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; + +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; + +import type { inferSSRProps } from "@lib/types/inferSSRProps"; + +import type { getServerSideProps } from "@server/lib/auth/sso/[provider]/getServerSideProps"; + +export type SSOProviderPageProps = inferSSRProps; + +export default function Provider(props: SSOProviderPageProps) { + const searchParams = useCompatSearchParams(); + const router = useRouter(); + + useEffect(() => { + const email = searchParams?.get("email"); + if (props.provider === "saml") { + if (!email) { + router.push(`/auth/error?error=Email not provided`); + return; + } + + if (!props.isSAMLLoginEnabled) { + router.push(`/auth/error?error=SAML login not enabled`); + return; + } + + signIn("saml", {}, { tenant: props.tenant, product: props.product }); + } else if (props.provider === "google" && email) { + signIn("google", {}, { login_hint: email }); + } else { + signIn(props.provider); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return null; +} diff --git a/apps/web/modules/d/[link]/d-type-view.tsx b/apps/web/modules/d/[link]/d-type-view.tsx new file mode 100644 index 0000000000..60355221f5 --- /dev/null +++ b/apps/web/modules/d/[link]/d-type-view.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { Booker } from "@calcom/atoms/monorepo"; +import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses"; +import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo"; + +import { type PageProps } from "@lib/d/[link]/[slug]/getServerSideProps"; + +export default function Type({ + slug, + isEmbed, + user, + booking, + isBrandingHidden, + isTeamEvent, + entity, + duration, + hashedLink, +}: PageProps) { + return ( +
+ + +
+ ); +} + +Type.isBookingPage = true; diff --git a/apps/web/modules/insights/insights-view.tsx b/apps/web/modules/insights/insights-view.tsx new file mode 100644 index 0000000000..21c9e96941 --- /dev/null +++ b/apps/web/modules/insights/insights-view.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { + AverageEventDurationChart, + BookingKPICards, + BookingStatusLineChart, + LeastBookedTeamMembersTable, + MostBookedTeamMembersTable, + PopularEventsTable, + HighestNoShowHostTable, + RecentFeedbackTable, + HighestRatedMembersTable, + LowestRatedMembersTable, +} from "@calcom/features/insights/components"; +import { FiltersProvider } from "@calcom/features/insights/context/FiltersProvider"; +import { Filters } from "@calcom/features/insights/filters"; +import Shell from "@calcom/features/shell/Shell"; +import { UpgradeTip } from "@calcom/features/tips"; +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc"; +import { Button, ButtonGroup } from "@calcom/ui"; +import { Icon } from "@calcom/ui"; + +import type { getServerSideProps } from "@lib/insights/getServerSideProps"; +import type { inferSSRProps } from "@lib/types/inferSSRProps"; + +export type PageProps = inferSSRProps; + +export default function InsightsPage() { + const { t } = useLocale(); + const { data: user } = trpc.viewer.me.useQuery(); + + const features = [ + { + icon: , + title: t("view_bookings_across"), + description: t("view_bookings_across_description"), + }, + { + icon: , + title: t("identify_booking_trends"), + description: t("identify_booking_trends_description"), + }, + { + icon: , + title: t("spot_popular_event_types"), + description: t("spot_popular_event_types_description"), + }, + ]; + + return ( +
+ + + + + + +
+ }> + {!user ? ( + <> + ) : ( + + + +
+ + + + +
+ + + +
+
+ + +
+ +
+ + + +
+ + {t("looking_for_more_insights")}{" "} + + {" "} + {t("contact_support")} + + +
+
+ )} + + + + ); +} diff --git a/apps/web/modules/signup-view.tsx b/apps/web/modules/signup-view.tsx new file mode 100644 index 0000000000..c77e837262 --- /dev/null +++ b/apps/web/modules/signup-view.tsx @@ -0,0 +1,661 @@ +"use client"; + +import { Analytics as DubAnalytics } from "@dub/analytics/react"; +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, + IS_EUROPE, + WEBAPP_URL, + CLOUDFLARE_SITE_ID, + WEBSITE_PRIVACY_POLICY_URL, + WEBSITE_TERMS_URL, + WEBSITE_URL, +} from "@calcom/lib/constants"; +import { isENVDev } from "@calcom/lib/env"; +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 type { getServerSideProps } from "@lib/signup/getServerSideProps"; + +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 && (!IS_EUROPE || COOKIE_CONSENT) ? ( + <> + {process.env.NEXT_PUBLIC_GTM_ID && ( + <> +