From 28acbe549ae6eff2a471276f6e7787a19488a7f4 Mon Sep 17 00:00:00 2001 From: DmytroHryshyn <125881252+DmytroHryshyn@users.noreply.github.com> Date: Thu, 16 Nov 2023 22:38:27 +0200 Subject: [PATCH] chore: [app dir bootstrapping 9]: replace useSearchParams with useCompatSearchParams hook (#12056) Co-authored-by: zomars --- apps/web/components/AppListCard.tsx | 5 +- apps/web/lib/hooks/useIsBookingPage.ts | 6 +- apps/web/lib/hooks/useRouterQuery.ts | 6 +- apps/web/lib/hooks/useToggleQuery.tsx | 4 +- apps/web/pages/500.tsx | 4 +- apps/web/pages/apps/[slug]/setup.tsx | 5 +- apps/web/pages/apps/categories/[category].tsx | 4 +- apps/web/pages/apps/installed/[category].tsx | 4 +- apps/web/pages/auth/error.tsx | 4 +- apps/web/pages/auth/login.tsx | 5 +- apps/web/pages/auth/oauth2/authorize.tsx | 4 +- apps/web/pages/auth/saml-idp.tsx | 5 +- apps/web/pages/auth/setup/index.tsx | 5 +- apps/web/pages/auth/sso/[provider].tsx | 5 +- apps/web/pages/auth/verify.tsx | 5 +- apps/web/pages/availability/[schedule].tsx | 5 +- apps/web/pages/availability/index.tsx | 5 +- apps/web/pages/booking/[uid].tsx | 5 +- apps/web/pages/event-types/index.tsx | 9 +-- apps/web/pages/signup.tsx | 4 +- .../alby/components/AlbyPaymentComponent.tsx | 4 +- packages/app-store/alby/pages/setup/index.tsx | 6 +- .../routing-forms/components/FormActions.tsx | 5 +- .../pages/routing-link/[...appPages].tsx | 5 +- .../embeds/embed-core/src/embed-iframe.ts | 4 +- .../embed-core/src/useCompatSearchParams.tsx | 21 ++++++ packages/embeds/embed-core/tsconfig.json | 1 + packages/embeds/embed-snippet/tsconfig.json | 1 + packages/features/apps/AdminAppsList.tsx | 4 +- .../features/bookings/Booker/utils/event.ts | 5 +- .../ee/payments/components/Payment.tsx | 5 +- .../ee/teams/components/AddNewTeamMembers.tsx | 7 +- .../ee/teams/components/TeamListItem.tsx | 4 +- .../ee/teams/components/TeamsListing.tsx | 5 +- .../ee/teams/pages/team-members-view.tsx | 5 +- packages/features/embed/Embed.tsx | 9 +-- .../eventtypes/components/DuplicateDialog.tsx | 4 +- .../insights/context/FiltersProvider.tsx | 5 +- .../settings/layouts/SettingsLayout.tsx | 5 +- .../webhooks/pages/webhook-edit-view.tsx | 5 +- .../webhooks/pages/webhook-new-view.tsx | 5 +- packages/lib/bookingSuccessRedirect.ts | 5 +- .../lib/hooks/useCompatSearchParams.test.ts | 67 +++++++++++++++++++ packages/lib/hooks/useCompatSearchParams.tsx | 20 ++++++ packages/lib/hooks/useRouterQuery.ts | 4 +- packages/lib/hooks/useUrlMatchesCurrentUrl.ts | 6 +- packages/ui/components/apps/AllApps.tsx | 7 +- .../components/createButton/CreateButton.tsx | 5 +- packages/ui/components/dialog/Dialog.tsx | 5 +- packages/ui/components/dialog/dialog.test.tsx | 6 ++ .../ui/components/form/wizard/WizardForm.tsx | 5 +- .../form/wizard/wizardForm.test.tsx | 6 ++ vitest.workspace.ts | 1 + 53 files changed, 254 insertions(+), 97 deletions(-) create mode 100644 packages/embeds/embed-core/src/useCompatSearchParams.tsx create mode 100644 packages/lib/hooks/useCompatSearchParams.test.ts create mode 100644 packages/lib/hooks/useCompatSearchParams.tsx diff --git a/apps/web/components/AppListCard.tsx b/apps/web/components/AppListCard.tsx index 2f9547fbb7..e9deaae260 100644 --- a/apps/web/components/AppListCard.tsx +++ b/apps/web/components/AppListCard.tsx @@ -1,4 +1,4 @@ -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import type { ReactNode } from "react"; import { useEffect, useRef, useState } from "react"; import { z } from "zod"; @@ -6,6 +6,7 @@ import { z } from "zod"; import type { CredentialOwner } from "@calcom/app-store/types"; import classNames from "@calcom/lib/classNames"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery"; import { Badge, ListItemText, Avatar } from "@calcom/ui"; @@ -56,7 +57,7 @@ export default function AppListCard(props: AppListCardProps) { const router = useRouter(); const [highlight, setHighlight] = useState(shouldHighlight && hl === slug); const timeoutRef = useRef(null); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const pathname = usePathname(); useEffect(() => { diff --git a/apps/web/lib/hooks/useIsBookingPage.ts b/apps/web/lib/hooks/useIsBookingPage.ts index 3f890bcedc..ffa6804e02 100644 --- a/apps/web/lib/hooks/useIsBookingPage.ts +++ b/apps/web/lib/hooks/useIsBookingPage.ts @@ -1,10 +1,12 @@ -import { usePathname, useSearchParams } from "next/navigation"; +import { usePathname } from "next/navigation"; + +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; export default function useIsBookingPage(): boolean { const pathname = usePathname(); const isBookingPage = ["/booking/", "/cancel", "/reschedule"].some((route) => pathname?.startsWith(route)); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const userParam = Boolean(searchParams?.get("user")); const teamParam = Boolean(searchParams?.get("team")); diff --git a/apps/web/lib/hooks/useRouterQuery.ts b/apps/web/lib/hooks/useRouterQuery.ts index 3bd40e57b3..1ec5a909b2 100644 --- a/apps/web/lib/hooks/useRouterQuery.ts +++ b/apps/web/lib/hooks/useRouterQuery.ts @@ -1,8 +1,10 @@ -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useCallback } from "react"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; + export default function useRouterQuery(name: T) { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const pathname = usePathname(); const router = useRouter(); diff --git a/apps/web/lib/hooks/useToggleQuery.tsx b/apps/web/lib/hooks/useToggleQuery.tsx index 29e3f4fabf..8e705851a9 100644 --- a/apps/web/lib/hooks/useToggleQuery.tsx +++ b/apps/web/lib/hooks/useToggleQuery.tsx @@ -1,7 +1,7 @@ -import { useSearchParams } from "next/navigation"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; export function useToggleQuery(name: string) { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); return { isOn: searchParams?.get(name) === "1", diff --git a/apps/web/pages/500.tsx b/apps/web/pages/500.tsx index a7ba940d2f..d91524a60d 100644 --- a/apps/web/pages/500.tsx +++ b/apps/web/pages/500.tsx @@ -1,7 +1,7 @@ import Head from "next/head"; -import { useSearchParams } from "next/navigation"; import { APP_NAME } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button, showToast } from "@calcom/ui"; import { Copy } from "@calcom/ui/components/icon"; @@ -9,7 +9,7 @@ import { Copy } from "@calcom/ui/components/icon"; import PageWrapper from "@components/PageWrapper"; export default function Error500() { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t } = useLocale(); return ( diff --git a/apps/web/pages/apps/[slug]/setup.tsx b/apps/web/pages/apps/[slug]/setup.tsx index 4942585a4c..31d8c78a11 100644 --- a/apps/web/pages/apps/[slug]/setup.tsx +++ b/apps/web/pages/apps/[slug]/setup.tsx @@ -1,15 +1,16 @@ import type { InferGetServerSidePropsType } from "next"; import { useSession } from "next-auth/react"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { AppSetupPage } from "@calcom/app-store/_pages/setup"; import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { HeadSeo } from "@calcom/ui"; import PageWrapper from "@components/PageWrapper"; export default function SetupInformation(props: InferGetServerSidePropsType) { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const router = useRouter(); const slug = searchParams?.get("slug") as string; const { status } = useSession(); diff --git a/apps/web/pages/apps/categories/[category].tsx b/apps/web/pages/apps/categories/[category].tsx index da82248c82..81de5e70c8 100644 --- a/apps/web/pages/apps/categories/[category].tsx +++ b/apps/web/pages/apps/categories/[category].tsx @@ -1,10 +1,10 @@ import { Prisma } from "@prisma/client"; import type { GetStaticPropsContext, InferGetStaticPropsType } from "next"; import Link from "next/link"; -import { useSearchParams } from "next/navigation"; import { getAppRegistry } from "@calcom/app-store/_appRegistry"; import Shell from "@calcom/features/shell/Shell"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import prisma from "@calcom/prisma"; import { AppCategories } from "@calcom/prisma/enums"; @@ -13,7 +13,7 @@ import { AppCard, SkeletonText } from "@calcom/ui"; import PageWrapper from "@components/PageWrapper"; export default function Apps({ apps }: InferGetStaticPropsType) { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t, isLocaleReady } = useLocale(); const category = searchParams?.get("category"); diff --git a/apps/web/pages/apps/installed/[category].tsx b/apps/web/pages/apps/installed/[category].tsx index 83ad068cdb..8e6d8c2404 100644 --- a/apps/web/pages/apps/installed/[category].tsx +++ b/apps/web/pages/apps/installed/[category].tsx @@ -1,8 +1,8 @@ -import { useSearchParams } from "next/navigation"; import { useReducer } from "react"; import { z } from "zod"; import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { AppCategories } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; @@ -122,7 +122,7 @@ type ModalState = { }; export default function InstalledApps() { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t } = useLocale(); const category = searchParams?.get("category") as querySchemaType["category"]; const categoryList: AppCategories[] = Object.values(AppCategories).filter((category) => { diff --git a/apps/web/pages/auth/error.tsx b/apps/web/pages/auth/error.tsx index 20e5f77516..502fead3ff 100644 --- a/apps/web/pages/auth/error.tsx +++ b/apps/web/pages/auth/error.tsx @@ -1,8 +1,8 @@ import type { GetStaticPropsContext } from "next"; import Link from "next/link"; -import { useSearchParams } from "next/navigation"; import z from "zod"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button } from "@calcom/ui"; import { X } from "@calcom/ui/components/icon"; @@ -18,7 +18,7 @@ const querySchema = z.object({ export default function Error() { const { t } = useLocale(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { error } = querySchema.parse(searchParams); const isTokenVerificationError = error?.toLowerCase() === "verification"; const errorMsg = isTokenVerificationError ? t("token_invalid_expired") : t("error_during_login"); diff --git a/apps/web/pages/auth/login.tsx b/apps/web/pages/auth/login.tsx index 1fa1eab0cb..327486bc8c 100644 --- a/apps/web/pages/auth/login.tsx +++ b/apps/web/pages/auth/login.tsx @@ -4,7 +4,7 @@ import { jwtVerify } from "jose"; import type { GetServerSidePropsContext } from "next"; import { getCsrfToken, signIn } from "next-auth/react"; import Link from "next/link"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import type { CSSProperties } from "react"; import { useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; @@ -17,6 +17,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml"; import { WEBAPP_URL, WEBSITE_URL, HOSTED_CAL_FEATURES } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import prisma from "@calcom/prisma"; @@ -53,7 +54,7 @@ export default function Login({ totpEmail, }: // eslint-disable-next-line @typescript-eslint/ban-types inferSSRProps & WithNonceProps<{}>) { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t } = useLocale(); const router = useRouter(); const formSchema = z diff --git a/apps/web/pages/auth/oauth2/authorize.tsx b/apps/web/pages/auth/oauth2/authorize.tsx index e34635540c..18b9fa1404 100644 --- a/apps/web/pages/auth/oauth2/authorize.tsx +++ b/apps/web/pages/auth/oauth2/authorize.tsx @@ -1,9 +1,9 @@ import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; -import { useSearchParams } from "next/navigation"; import { useState, useEffect } from "react"; import { APP_NAME } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { Avatar, Button, Select } from "@calcom/ui"; @@ -16,7 +16,7 @@ export default function Authorize() { const { status } = useSession(); const router = useRouter(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const client_id = searchParams?.get("client_id") as string; const state = searchParams?.get("state") as string; diff --git a/apps/web/pages/auth/saml-idp.tsx b/apps/web/pages/auth/saml-idp.tsx index 77b0ac0650..6942574a60 100644 --- a/apps/web/pages/auth/saml-idp.tsx +++ b/apps/web/pages/auth/saml-idp.tsx @@ -1,12 +1,13 @@ import { signIn } from "next-auth/react"; -import { useSearchParams } from "next/navigation"; import { useEffect } from "react"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; + import PageWrapper from "@components/PageWrapper"; // To handle the IdP initiated login flow callback export default function Page() { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); useEffect(() => { const code = searchParams?.get("code"); diff --git a/apps/web/pages/auth/setup/index.tsx b/apps/web/pages/auth/setup/index.tsx index 4badbd1804..bc128f4cff 100644 --- a/apps/web/pages/auth/setup/index.tsx +++ b/apps/web/pages/auth/setup/index.tsx @@ -1,11 +1,12 @@ import type { GetServerSidePropsContext } from "next"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useState } from "react"; import AdminAppsList from "@calcom/features/apps/AdminAppsList"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { getDeploymentKey } from "@calcom/features/ee/deployment/lib/getDeploymentKey"; import { APP_NAME } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import prisma from "@calcom/prisma"; import { UserPermissionRole } from "@calcom/prisma/enums"; @@ -21,7 +22,7 @@ import { ssrInit } from "@server/lib/ssr"; function useSetStep() { const router = useRouter(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const pathname = usePathname(); const setStep = (newStep = 1) => { const _searchParams = new URLSearchParams(searchParams ?? undefined); diff --git a/apps/web/pages/auth/sso/[provider].tsx b/apps/web/pages/auth/sso/[provider].tsx index ee9aa08d27..b2d8b418da 100644 --- a/apps/web/pages/auth/sso/[provider].tsx +++ b/apps/web/pages/auth/sso/[provider].tsx @@ -1,6 +1,6 @@ import type { GetServerSidePropsContext } from "next"; import { signIn } from "next-auth/react"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { useEffect } from "react"; import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils"; @@ -9,6 +9,7 @@ import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomain import stripe from "@calcom/features/ee/payments/server/stripe"; import { hostedCal, isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml"; import { ssoTenantProduct } from "@calcom/features/ee/sso/lib/sso"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { checkUsername } from "@calcom/lib/server/checkUsername"; import prisma from "@calcom/prisma"; @@ -22,7 +23,7 @@ import { ssrInit } from "@server/lib/ssr"; export type SSOProviderPageProps = inferSSRProps; export default function Provider(props: SSOProviderPageProps) { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const router = useRouter(); useEffect(() => { diff --git a/apps/web/pages/auth/verify.tsx b/apps/web/pages/auth/verify.tsx index d0ce633d2f..85b17a4be0 100644 --- a/apps/web/pages/auth/verify.tsx +++ b/apps/web/pages/auth/verify.tsx @@ -1,10 +1,11 @@ import { signIn } from "next-auth/react"; import Head from "next/head"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import z from "zod"; import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; import { trpc } from "@calcom/trpc/react"; import { Button, showToast } from "@calcom/ui"; @@ -54,7 +55,7 @@ const querySchema = z.object({ }); export default function Verify() { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const pathname = usePathname(); const router = useRouter(); const routerQuery = useRouterQuery(); diff --git a/apps/web/pages/availability/[schedule].tsx b/apps/web/pages/availability/[schedule].tsx index da393efe05..f3502b6f9a 100644 --- a/apps/web/pages/availability/[schedule].tsx +++ b/apps/web/pages/availability/[schedule].tsx @@ -1,4 +1,4 @@ -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { useState } from "react"; import { Controller, useFieldArray, useForm } from "react-hook-form"; @@ -8,6 +8,7 @@ import Schedule from "@calcom/features/schedules/components/Schedule"; import Shell from "@calcom/features/shell/Shell"; import { classNames } from "@calcom/lib"; import { availabilityAsString } from "@calcom/lib/availability"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; import { trpc } from "@calcom/trpc/react"; @@ -83,7 +84,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => { }; export default function Availability() { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t, i18n } = useLocale(); const router = useRouter(); const utils = trpc.useContext(); diff --git a/apps/web/pages/availability/index.tsx b/apps/web/pages/availability/index.tsx index 4e4738f632..d368c2a233 100644 --- a/apps/web/pages/availability/index.tsx +++ b/apps/web/pages/availability/index.tsx @@ -1,11 +1,12 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; -import { useRouter, useSearchParams, usePathname } from "next/navigation"; +import { useRouter, usePathname } from "next/navigation"; import { useCallback } from "react"; import { getLayout } from "@calcom/features/MainLayout"; import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules"; import { ShellMain } from "@calcom/features/shell/Shell"; import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/components/AvailabilitySliderTable"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; import type { RouterOutputs } from "@calcom/trpc/react"; @@ -131,7 +132,7 @@ const WithQuery = withQuery(trpc.viewer.availability.list as any); export default function AvailabilityPage() { const { t } = useLocale(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const router = useRouter(); const pathname = usePathname(); diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx index 44094705b9..029652211a 100644 --- a/apps/web/pages/booking/[uid].tsx +++ b/apps/web/pages/booking/[uid].tsx @@ -4,7 +4,7 @@ import { createEvent } from "ics"; import type { GetServerSidePropsContext } from "next"; import { useSession } from "next-auth/react"; import Link from "next/link"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { RRule } from "rrule"; import { z } from "zod"; @@ -36,6 +36,7 @@ import { } from "@calcom/lib/date-fns"; import { getDefaultEvent } from "@calcom/lib/defaultEvents"; import useGetBrandingColours from "@calcom/lib/getBrandColours"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; import useTheme from "@calcom/lib/hooks/useTheme"; @@ -98,7 +99,7 @@ export default function Success(props: SuccessProps) { const router = useRouter(); const routerQuery = useRouterQuery(); const pathname = usePathname(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { allRemainingBookings, isSuccessBookingPage, diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx index 468e1fde5d..7bde895793 100644 --- a/apps/web/pages/event-types/index.tsx +++ b/apps/web/pages/event-types/index.tsx @@ -2,7 +2,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import type { User } from "@prisma/client"; import { Trans } from "next-i18next"; import Link from "next/link"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import type { FC } from "react"; import { memo, useEffect, useState } from "react"; import { z } from "zod"; @@ -19,6 +19,7 @@ import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsF import { ShellMain } from "@calcom/features/shell/Shell"; import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants"; import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useMediaQuery from "@calcom/lib/hooks/useMediaQuery"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; @@ -208,7 +209,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL const { t } = useLocale(); const router = useRouter(); const pathname = usePathname(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const orgBranding = useOrgBranding(); const [parent] = useAutoAnimate(); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); @@ -840,7 +841,7 @@ const Main = ({ filters: ReturnType; }) => { const isMobile = useMediaQuery("(max-width: 768px)"); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const orgBranding = useOrgBranding(); if (!data || status === "loading") { @@ -904,7 +905,7 @@ const Main = ({ const EventTypesPage = () => { const { t } = useLocale(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { open } = useIntercom(); const { data: user } = useMeQuery(); const [showProfileBanner, setShowProfileBanner] = useState(false); diff --git a/apps/web/pages/signup.tsx b/apps/web/pages/signup.tsx index 21f3459f1f..0b0cb0e5a8 100644 --- a/apps/web/pages/signup.tsx +++ b/apps/web/pages/signup.tsx @@ -1,7 +1,6 @@ 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"; @@ -13,6 +12,7 @@ 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 { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import slugify from "@calcom/lib/slugify"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; @@ -53,7 +53,7 @@ function addOrUpdateQueryParam(url: string, key: string, value: string) { } export default function Signup({ prepopulateFormValues, token, orgSlug, orgAutoAcceptEmail }: SignupProps) { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const telemetry = useTelemetry(); const { t, i18n } = useLocale(); const flags = useFlagMap(); diff --git a/packages/app-store/alby/components/AlbyPaymentComponent.tsx b/packages/app-store/alby/components/AlbyPaymentComponent.tsx index 34599a9800..30e7063535 100644 --- a/packages/app-store/alby/components/AlbyPaymentComponent.tsx +++ b/packages/app-store/alby/components/AlbyPaymentComponent.tsx @@ -1,11 +1,11 @@ import Link from "next/link"; -import { useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; import QRCode from "react-qr-code"; import z from "zod"; import type { PaymentPageProps } from "@calcom/features/ee/payments/pages/payment"; import { useBookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useCopy } from "@calcom/lib/hooks/useCopy"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; @@ -130,7 +130,7 @@ type PaymentCheckerProps = PaymentPageProps; function PaymentChecker(props: PaymentCheckerProps) { // TODO: move booking success code to a common lib function // TODO: subscribe rather than polling - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const bookingSuccessRedirect = useBookingSuccessRedirect(); const utils = trpc.useContext(); const { t } = useLocale(); diff --git a/packages/app-store/alby/pages/setup/index.tsx b/packages/app-store/alby/pages/setup/index.tsx index fdd8403b03..ae447708c3 100644 --- a/packages/app-store/alby/pages/setup/index.tsx +++ b/packages/app-store/alby/pages/setup/index.tsx @@ -1,10 +1,10 @@ import { auth, Client, webln } from "@getalby/sdk"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { useSearchParams } from "next/navigation"; import { useState, useCallback, useEffect } from "react"; import { Toaster } from "react-hot-toast"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; import { Badge, Button, showToast } from "@calcom/ui"; @@ -20,7 +20,7 @@ export interface IAlbySetupProps { } export default function AlbySetup(props: IAlbySetupProps) { - const params = useSearchParams(); + const params = useCompatSearchParams(); if (params?.get("callback") === "true") { return ; } @@ -30,7 +30,7 @@ export default function AlbySetup(props: IAlbySetupProps) { function AlbySetupCallback() { const [error, setError] = useState(null); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); useEffect(() => { if (!searchParams) { diff --git a/packages/app-store/routing-forms/components/FormActions.tsx b/packages/app-store/routing-forms/components/FormActions.tsx index 6c96a221eb..088d374231 100644 --- a/packages/app-store/routing-forms/components/FormActions.tsx +++ b/packages/app-store/routing-forms/components/FormActions.tsx @@ -1,5 +1,5 @@ import type { App_RoutingForms_Form } from "@prisma/client"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { createContext, forwardRef, useContext, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { v4 as uuidv4 } from "uuid"; @@ -9,6 +9,7 @@ import { useOrgBranding } from "@calcom/features/ee/organizations/context/provid import { RoutingFormEmbedButton, RoutingFormEmbedDialog } from "@calcom/features/embed/RoutingFormEmbed"; import { classNames } from "@calcom/lib"; import { CAL_URL } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; import { trpc } from "@calcom/trpc/react"; @@ -46,7 +47,7 @@ const newFormModalQuerySchema = z.object({ export const useOpenModal = () => { const router = useRouter(); const pathname = usePathname(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const openModal = (option: z.infer) => { const newQuery = new URLSearchParams(searchParams ?? undefined); newQuery.set("dialog", "new-form"); diff --git a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx index e740da2f83..ae2aab94ec 100644 --- a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx @@ -1,5 +1,5 @@ import Head from "next/head"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import type { FormEvent } from "react"; import { useEffect, useRef, useState } from "react"; import { Toaster } from "react-hot-toast"; @@ -9,6 +9,7 @@ import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe"; import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import classNames from "@calcom/lib/classNames"; import useGetBrandingColours from "@calcom/lib/getBrandColours"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useTheme from "@calcom/lib/hooks/useTheme"; import { trpc } from "@calcom/trpc/react"; @@ -296,7 +297,7 @@ export const getServerSideProps = async function getServerSideProps( }; const usePrefilledResponse = (form: Props["form"]) => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const prefillResponse: Response = {}; // Prefill the form from query params diff --git a/packages/embeds/embed-core/src/embed-iframe.ts b/packages/embeds/embed-core/src/embed-iframe.ts index bb5cb8c655..a4d72bf32c 100644 --- a/packages/embeds/embed-core/src/embed-iframe.ts +++ b/packages/embeds/embed-core/src/embed-iframe.ts @@ -1,10 +1,10 @@ import { useRouter } from "next/navigation"; -import { useSearchParams } from "next/navigation"; import { useEffect, useRef, useState, useCallback } from "react"; import type { Message } from "./embed"; import { sdkActionManager } from "./sdk-event"; import type { EmbedThemeConfig, UiConfig, EmbedNonStylesConfig, BookerLayouts, EmbedStyles } from "./types"; +import { useCompatSearchParams } from "./useCompatSearchParams"; type SetStyles = React.Dispatch>; type setNonStylesConfig = React.Dispatch>; @@ -208,7 +208,7 @@ const useUrlChange = (callback: (newUrl: string) => void) => { }; export const useEmbedTheme = () => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const [theme, setTheme] = useState( embedStore.theme || (searchParams?.get("theme") as typeof embedStore.theme) ); diff --git a/packages/embeds/embed-core/src/useCompatSearchParams.tsx b/packages/embeds/embed-core/src/useCompatSearchParams.tsx new file mode 100644 index 0000000000..68bb1e6d6d --- /dev/null +++ b/packages/embeds/embed-core/src/useCompatSearchParams.tsx @@ -0,0 +1,21 @@ +// this file is copied from '@calcom/lib/hooks/useCompatSearchParams.tsx' +import { ReadonlyURLSearchParams, useParams, useSearchParams } from "next/navigation"; + +export const useCompatSearchParams = () => { + const _searchParams = useSearchParams() ?? new URLSearchParams(); + const params = useParams() ?? {}; + + const searchParams = new URLSearchParams(_searchParams.toString()); + Object.getOwnPropertyNames(params).forEach((key) => { + searchParams.delete(key); + + const param = params[key]; + const paramArr = typeof param === "string" ? param.split("/") : param; + + paramArr.forEach((p) => { + searchParams.append(key, p); + }); + }); + + return new ReadonlyURLSearchParams(searchParams); +}; diff --git a/packages/embeds/embed-core/tsconfig.json b/packages/embeds/embed-core/tsconfig.json index 154a1cb01f..05fc61c244 100644 --- a/packages/embeds/embed-core/tsconfig.json +++ b/packages/embeds/embed-core/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "@calcom/tsconfig/base.json", "compilerOptions": { + "jsx": "react", "target": "ES2015", "module": "esnext", "moduleResolution": "Node", diff --git a/packages/embeds/embed-snippet/tsconfig.json b/packages/embeds/embed-snippet/tsconfig.json index a1dfc8123e..b2907d9204 100644 --- a/packages/embeds/embed-snippet/tsconfig.json +++ b/packages/embeds/embed-snippet/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "@calcom/tsconfig/base.json", "compilerOptions": { + "jsx": "react", "target": "ES2015", "baseUrl": ".", "module": "ESNext", diff --git a/packages/features/apps/AdminAppsList.tsx b/packages/features/apps/AdminAppsList.tsx index 5c96da95bd..46277b242e 100644 --- a/packages/features/apps/AdminAppsList.tsx +++ b/packages/features/apps/AdminAppsList.tsx @@ -1,7 +1,6 @@ import { zodResolver } from "@hookform/resolvers/zod"; // eslint-disable-next-line no-restricted-imports import { noop } from "lodash"; -import { useSearchParams } from "next/navigation"; import type { FC } from "react"; import { useReducer, useState } from "react"; import { Controller, useForm } from "react-hook-form"; @@ -10,6 +9,7 @@ import { z } from "zod"; import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavigation"; import { appKeysSchemas } from "@calcom/app-store/apps.keys-schemas.generated"; import { classNames as cs } from "@calcom/lib"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { AppCategories } from "@calcom/prisma/enums"; import type { RouterOutputs } from "@calcom/trpc/react"; @@ -266,7 +266,7 @@ interface EditModalState extends Pick { } const AdminAppsListContainer = () => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t } = useLocale(); const category = searchParams?.get("category") || AppCategories.calendar; diff --git a/packages/features/bookings/Booker/utils/event.ts b/packages/features/bookings/Booker/utils/event.ts index b32440197c..dc6eb24be1 100644 --- a/packages/features/bookings/Booker/utils/event.ts +++ b/packages/features/bookings/Booker/utils/event.ts @@ -1,7 +1,8 @@ -import { useSearchParams, usePathname } from "next/navigation"; +import { usePathname } from "next/navigation"; import { shallow } from "zustand/shallow"; import { useSchedule } from "@calcom/features/schedules"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { trpc } from "@calcom/trpc/react"; import { useTimePreferences } from "../../lib/timePreferences"; @@ -61,7 +62,7 @@ export const useScheduleForEvent = ({ (state) => [state.username, state.eventSlug, state.month, state.selectedDuration], shallow ); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const rescheduleUid = searchParams?.get("rescheduleUid"); const pathname = usePathname(); diff --git a/packages/features/ee/payments/components/Payment.tsx b/packages/features/ee/payments/components/Payment.tsx index 874e795063..62d4da5518 100644 --- a/packages/features/ee/payments/components/Payment.tsx +++ b/packages/features/ee/payments/components/Payment.tsx @@ -2,13 +2,14 @@ import type { Payment } from "@prisma/client"; import type { EventType } from "@prisma/client"; import { Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js"; import type { StripeElementLocale } from "@stripe/stripe-js"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import type { SyntheticEvent } from "react"; import { useEffect, useState } from "react"; import getStripe from "@calcom/app-store/stripepayment/lib/client"; import { getBookingRedirectExtraParams, useBookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect"; import { CAL_URL } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button, CheckboxField } from "@calcom/ui"; @@ -69,7 +70,7 @@ const PaymentForm = (props: Props) => { } = props; const { t, i18n } = useLocale(); const router = useRouter(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const [state, setState] = useState({ status: "idle" }); const [isCanceling, setIsCanceling] = useState(false); const stripe = useStripe(); diff --git a/packages/features/ee/teams/components/AddNewTeamMembers.tsx b/packages/features/ee/teams/components/AddNewTeamMembers.tsx index 1b892aa1be..750f8ecbae 100644 --- a/packages/features/ee/teams/components/AddNewTeamMembers.tsx +++ b/packages/features/ee/teams/components/AddNewTeamMembers.tsx @@ -1,5 +1,5 @@ import { useSession } from "next-auth/react"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { useState } from "react"; import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; @@ -8,6 +8,7 @@ import MemberInvitationModal from "@calcom/features/ee/teams/components/MemberIn import { classNames } from "@calcom/lib"; import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants"; import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { MembershipRole } from "@calcom/prisma/enums"; import type { RouterOutputs } from "@calcom/trpc/react"; @@ -30,7 +31,7 @@ type FormValues = { }; const AddNewTeamMembers = () => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const session = useSession(); const teamId = searchParams?.get("id") ? Number(searchParams.get("id")) : -1; const teamQuery = trpc.viewer.teams.get.useQuery( @@ -49,7 +50,7 @@ export const AddNewTeamMembersForm = ({ defaultValues: FormValues; teamId: number; }) => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t, i18n } = useLocale(); const router = useRouter(); diff --git a/packages/features/ee/teams/components/TeamListItem.tsx b/packages/features/ee/teams/components/TeamListItem.tsx index fcb88ba118..6be6a2344d 100644 --- a/packages/features/ee/teams/components/TeamListItem.tsx +++ b/packages/features/ee/teams/components/TeamListItem.tsx @@ -1,5 +1,4 @@ import Link from "next/link"; -import { useSearchParams } from "next/navigation"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -7,6 +6,7 @@ import InviteLinkSettingsModal from "@calcom/ee/teams/components/InviteLinkSetti import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal"; import classNames from "@calcom/lib/classNames"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { MembershipRole } from "@calcom/prisma/enums"; import type { RouterOutputs } from "@calcom/trpc/react"; @@ -53,7 +53,7 @@ interface Props { } export default function TeamListItem(props: Props) { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t, i18n } = useLocale(); const utils = trpc.useContext(); const team = props.team; diff --git a/packages/features/ee/teams/components/TeamsListing.tsx b/packages/features/ee/teams/components/TeamsListing.tsx index f121ea9e8f..5b909484c7 100644 --- a/packages/features/ee/teams/components/TeamsListing.tsx +++ b/packages/features/ee/teams/components/TeamsListing.tsx @@ -1,7 +1,8 @@ -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { Alert, Button, ButtonGroup, EmptyScreen, Label, showToast } from "@calcom/ui"; @@ -12,7 +13,7 @@ import SkeletonLoaderTeamList from "./SkeletonloaderTeamList"; import TeamList from "./TeamList"; export function TeamsListing() { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const token = searchParams?.get("token"); const { t } = useLocale(); const trpcContext = trpc.useContext(); diff --git a/packages/features/ee/teams/pages/team-members-view.tsx b/packages/features/ee/teams/pages/team-members-view.tsx index 6b7ff9b14a..553a8a449c 100644 --- a/packages/features/ee/teams/pages/team-members-view.tsx +++ b/packages/features/ee/teams/pages/team-members-view.tsx @@ -1,7 +1,8 @@ import { useSession } from "next-auth/react"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { useState } from "react"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; import { MembershipRole } from "@calcom/prisma/enums"; @@ -73,7 +74,7 @@ function MembersList(props: MembersListProps) { } const MembersView = () => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t, i18n } = useLocale(); const router = useRouter(); diff --git a/packages/features/embed/Embed.tsx b/packages/features/embed/Embed.tsx index 2a39590dc9..50e023ba76 100644 --- a/packages/features/embed/Embed.tsx +++ b/packages/features/embed/Embed.tsx @@ -1,7 +1,7 @@ import { Collapsible, CollapsibleContent } from "@radix-ui/react-collapsible"; import classNames from "classnames"; import { useSession } from "next-auth/react"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import type { RefObject } from "react"; import { createRef, useRef, useState } from "react"; import type { ControlProps } from "react-select"; @@ -21,6 +21,7 @@ import { useNonEmptyScheduleDays } from "@calcom/features/schedules"; import { useSlotsForDate } from "@calcom/features/schedules/lib/use-schedule/useSlotsForDate"; import { APP_NAME, CAL_URL } from "@calcom/lib/constants"; import { weekdayToWeekIndex } from "@calcom/lib/date-fns"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { BookerLayouts } from "@calcom/prisma/zod-utils"; import type { RouterOutputs } from "@calcom/trpc/react"; @@ -57,7 +58,7 @@ const queryParamsForDialog = ["embedType", "embedTabName", "embedUrl", "eventId" function useRouterHelpers() { const router = useRouter(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const pathname = usePathname(); const goto = (newSearchParams: Record) => { @@ -507,7 +508,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({ types: EmbedTypes; }) => { const { t } = useLocale(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const pathname = usePathname(); const { goto, removeQueryParams } = useRouterHelpers(); const iframeRef = useRef(null); @@ -1095,7 +1096,7 @@ export const EmbedDialog = ({ tabs: EmbedTabs; eventTypeHideOptionDisabled: boolean; }) => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const embedUrl = searchParams?.get("embedUrl") as string; return ( diff --git a/packages/features/eventtypes/components/DuplicateDialog.tsx b/packages/features/eventtypes/components/DuplicateDialog.tsx index 86f1e7da9e..0095b8b340 100644 --- a/packages/features/eventtypes/components/DuplicateDialog.tsx +++ b/packages/features/eventtypes/components/DuplicateDialog.tsx @@ -1,9 +1,9 @@ -import { useSearchParams } from "next/navigation"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery"; import { HttpError } from "@calcom/lib/http-error"; @@ -33,7 +33,7 @@ const querySchema = z.object({ }); const DuplicateDialog = () => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t } = useLocale(); const router = useRouter(); const [firstRender, setFirstRender] = useState(true); diff --git a/packages/features/insights/context/FiltersProvider.tsx b/packages/features/insights/context/FiltersProvider.tsx index 2eec6e91b3..98ed7542a7 100644 --- a/packages/features/insights/context/FiltersProvider.tsx +++ b/packages/features/insights/context/FiltersProvider.tsx @@ -1,8 +1,9 @@ -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useState } from "react"; import { z } from "zod"; import dayjs from "@calcom/dayjs"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { trpc } from "@calcom/trpc"; import type { FilterContextType } from "./provider"; @@ -21,7 +22,7 @@ const querySchema = z.object({ export function FiltersProvider({ children }: { children: React.ReactNode }) { // searchParams to get initial values from query params const utils = trpc.useContext(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const router = useRouter(); const pathname = usePathname(); diff --git a/packages/features/settings/layouts/SettingsLayout.tsx b/packages/features/settings/layouts/SettingsLayout.tsx index 25f14bc57f..95f217cb15 100644 --- a/packages/features/settings/layouts/SettingsLayout.tsx +++ b/packages/features/settings/layouts/SettingsLayout.tsx @@ -1,7 +1,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible"; import { useSession } from "next-auth/react"; import Link from "next/link"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import type { ComponentProps } from "react"; import React, { Suspense, useEffect, useState } from "react"; @@ -10,6 +10,7 @@ import Shell from "@calcom/features/shell/Shell"; import { classNames } from "@calcom/lib"; import { HOSTED_CAL_FEATURES, WEBAPP_URL } from "@calcom/lib/constants"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { IdentityProvider, MembershipRole, UserPermissionRole } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; @@ -194,7 +195,7 @@ const SettingsSidebarContainer = ({ navigationIsOpenedOnMobile, bannersHeight, }: SettingsSidebarContainerProps) => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t } = useLocale(); const tabsWithPermissions = useTabs(); const [teamMenuState, setTeamMenuState] = diff --git a/packages/features/webhooks/pages/webhook-edit-view.tsx b/packages/features/webhooks/pages/webhook-edit-view.tsx index e44ffeb5e6..8da185a933 100644 --- a/packages/features/webhooks/pages/webhook-edit-view.tsx +++ b/packages/features/webhooks/pages/webhook-edit-view.tsx @@ -1,6 +1,7 @@ -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { APP_NAME } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { Meta, showToast, SkeletonContainer } from "@calcom/ui"; @@ -11,7 +12,7 @@ import WebhookForm from "../components/WebhookForm"; import { subscriberUrlReserved } from "../lib/subscriberUrlReserved"; const EditWebhook = () => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const id = searchParams?.get("id"); if (!id) return ; diff --git a/packages/features/webhooks/pages/webhook-new-view.tsx b/packages/features/webhooks/pages/webhook-new-view.tsx index 17574ab183..e6c35c80a4 100644 --- a/packages/features/webhooks/pages/webhook-new-view.tsx +++ b/packages/features/webhooks/pages/webhook-new-view.tsx @@ -1,7 +1,8 @@ import { useSession } from "next-auth/react"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { APP_NAME } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { Meta, showToast, SkeletonContainer, SkeletonText } from "@calcom/ui"; @@ -23,7 +24,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st ); }; const NewWebhookView = () => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t } = useLocale(); const utils = trpc.useContext(); const router = useRouter(); diff --git a/packages/lib/bookingSuccessRedirect.ts b/packages/lib/bookingSuccessRedirect.ts index b1e3c6a8a0..9d1095ee2b 100644 --- a/packages/lib/bookingSuccessRedirect.ts +++ b/packages/lib/bookingSuccessRedirect.ts @@ -1,8 +1,9 @@ import type { EventType } from "@prisma/client"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import type { PaymentPageProps } from "@calcom/ee/payments/pages/payment"; import type { BookingResponse } from "@calcom/features/bookings/types"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; function getNewSeachParams(args: { query: Record; @@ -41,7 +42,7 @@ export const getBookingRedirectExtraParams = (booking: SuccessRedirectBookingTyp export const useBookingSuccessRedirect = () => { const router = useRouter(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const bookingSuccessRedirect = ({ successRedirectUrl, query, diff --git a/packages/lib/hooks/useCompatSearchParams.test.ts b/packages/lib/hooks/useCompatSearchParams.test.ts new file mode 100644 index 0000000000..72307663d0 --- /dev/null +++ b/packages/lib/hooks/useCompatSearchParams.test.ts @@ -0,0 +1,67 @@ +import { renderHook } from "@testing-library/react-hooks"; +import { vi } from "vitest"; +import { describe, expect, it } from "vitest"; + +import { useCompatSearchParams } from "./useCompatSearchParams"; + +vi.mock("next/navigation", () => ({ + ReadonlyURLSearchParams: vi.fn((a) => a), +})); + +describe("useCompatSearchParams hook", () => { + it("should return the searchParams in next@13.4.6 Pages Router, SSR", async () => { + const navigation = await import("next/navigation"); + + navigation.useSearchParams = vi.fn().mockReturnValue(new URLSearchParams("a=a&b=b")); + navigation.useParams = vi.fn().mockReturnValue(null); + + const { result } = renderHook(() => useCompatSearchParams()); + + expect(result.current.toString()).toEqual("a=a&b=b"); + }); + + it("should return both searchParams and params in next@13.4.6 App Router, SSR", async () => { + const navigation = await import("next/navigation"); + + navigation.useSearchParams = vi.fn().mockReturnValue(new URLSearchParams("a=a")); + navigation.useParams = vi.fn().mockReturnValue({ b: "b" }); + + const { result } = renderHook(() => useCompatSearchParams()); + + expect(result.current.toString()).toEqual("a=a&b=b"); + }); + + it("params should always override searchParams in case of conflicting keys", async () => { + const navigation = await import("next/navigation"); + + navigation.useSearchParams = vi.fn().mockReturnValue(new URLSearchParams("a=a")); + navigation.useParams = vi.fn().mockReturnValue({ a: "b" }); + + const { result } = renderHook(() => useCompatSearchParams()); + + expect(result.current.toString()).toEqual("a=b"); + }); + + it("should split paramsseparated with '/' (catch-all segments) in next@13.4.6 App Router, SSR", async () => { + const navigation = await import("next/navigation"); + + navigation.useSearchParams = vi.fn().mockReturnValue(new URLSearchParams()); + // in next@13.4.6 useParams will return params separated by `/` + navigation.useParams = vi.fn().mockReturnValue({ a: "a/b/c" }); + + const { result } = renderHook(() => useCompatSearchParams()); + + expect(result.current.getAll("a")).toEqual(["a", "b", "c"]); + }); + + it("should include params and searchParams in next@13.5.4, Pages/App Router, SSR", async () => { + const navigation = await import("next/navigation"); + + navigation.useSearchParams = vi.fn().mockReturnValue(new URLSearchParams("a=a")); + navigation.useParams = vi.fn().mockReturnValue({ b: "b" }); + + const { result } = renderHook(() => useCompatSearchParams()); + + expect(result.current.toString()).toEqual("a=a&b=b"); + }); +}); diff --git a/packages/lib/hooks/useCompatSearchParams.tsx b/packages/lib/hooks/useCompatSearchParams.tsx new file mode 100644 index 0000000000..032ba115a1 --- /dev/null +++ b/packages/lib/hooks/useCompatSearchParams.tsx @@ -0,0 +1,20 @@ +import { ReadonlyURLSearchParams, useParams, useSearchParams } from "next/navigation"; + +export const useCompatSearchParams = () => { + const _searchParams = useSearchParams() ?? new URLSearchParams(); + const params = useParams() ?? {}; + + const searchParams = new URLSearchParams(_searchParams.toString()); + Object.getOwnPropertyNames(params).forEach((key) => { + searchParams.delete(key); + + const param = params[key]; + const paramArr = typeof param === "string" ? param.split("/") : param; + + paramArr.forEach((p) => { + searchParams.append(key, p); + }); + }); + + return new ReadonlyURLSearchParams(searchParams); +}; diff --git a/packages/lib/hooks/useRouterQuery.ts b/packages/lib/hooks/useRouterQuery.ts index 5a923f284d..d1c1ebfd77 100644 --- a/packages/lib/hooks/useRouterQuery.ts +++ b/packages/lib/hooks/useRouterQuery.ts @@ -1,4 +1,4 @@ -import { useSearchParams } from "next/navigation"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; /** * An alternative to Object.fromEntries that allows duplicate keys. @@ -35,7 +35,7 @@ function fromEntriesWithDuplicateKeys(entries: IterableIterator<[string, string] * @returns {Object} routerQuery */ export const useRouterQuery = () => { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const routerQuery = fromEntriesWithDuplicateKeys(searchParams?.entries() ?? null); return routerQuery; }; diff --git a/packages/lib/hooks/useUrlMatchesCurrentUrl.ts b/packages/lib/hooks/useUrlMatchesCurrentUrl.ts index 7fc0c5fe2c..f022561350 100644 --- a/packages/lib/hooks/useUrlMatchesCurrentUrl.ts +++ b/packages/lib/hooks/useUrlMatchesCurrentUrl.ts @@ -1,12 +1,14 @@ "use client"; -import { usePathname, useSearchParams } from "next/navigation"; +import { usePathname } from "next/navigation"; + +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; export const useUrlMatchesCurrentUrl = (url: string) => { // I don't know why usePathname ReturnType doesn't include null. // It can certainly have null value https://nextjs.org/docs/app/api-reference/functions/use-pathname#:~:text=usePathname%20can%20return%20null%20when%20a%20fallback%20route%20is%20being%20rendered%20or%20when%20a%20pages%20directory%20page%20has%20been%20automatically%20statically%20optimized%20by%20Next.js%20and%20the%20router%20is%20not%20ready. const pathname = usePathname() as null | string; - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const query = searchParams?.toString(); let pathnameWithQuery; if (query) { diff --git a/packages/ui/components/apps/AllApps.tsx b/packages/ui/components/apps/AllApps.tsx index 4a906b1e87..665d87bd43 100644 --- a/packages/ui/components/apps/AllApps.tsx +++ b/packages/ui/components/apps/AllApps.tsx @@ -1,11 +1,12 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import type { AppCategories } from "@prisma/client"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import type { UIEvent } from "react"; import { useEffect, useRef, useState } from "react"; import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams"; import { classNames } from "@calcom/lib"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { AppFrontendPayload as App } from "@calcom/types/App"; import type { CredentialFrontendPayload as Credential } from "@calcom/types/Credential"; @@ -55,7 +56,7 @@ interface CategoryTabProps { function CategoryTab({ selectedCategory, categories, searchText }: CategoryTabProps) { const pathname = usePathname(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t } = useLocale(); const router = useRouter(); const { ref, calculateScroll, leftVisible, rightVisible } = useShouldShowArrows(); @@ -140,7 +141,7 @@ function CategoryTab({ selectedCategory, categories, searchText }: CategoryTabPr } export function AllApps({ apps, searchText, categories, userAdminTeams }: AllAppsPropsType) { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { t } = useLocale(); const [selectedCategory, setSelectedCategory] = useState(null); const [appsContainerRef, enableAnimation] = useAutoAnimate(); diff --git a/packages/ui/components/createButton/CreateButton.tsx b/packages/ui/components/createButton/CreateButton.tsx index f42b4225e9..51b4a3c973 100644 --- a/packages/ui/components/createButton/CreateButton.tsx +++ b/packages/ui/components/createButton/CreateButton.tsx @@ -1,6 +1,7 @@ -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { ButtonColor } from "@calcom/ui"; import { @@ -40,7 +41,7 @@ export type CreateBtnProps = { export function CreateButton(props: CreateBtnProps) { const { t } = useLocale(); const router = useRouter(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const pathname = usePathname(); const bookerUrl = useBookerUrl(); diff --git a/packages/ui/components/dialog/Dialog.tsx b/packages/ui/components/dialog/Dialog.tsx index 7565c0524c..06971f9745 100644 --- a/packages/ui/components/dialog/Dialog.tsx +++ b/packages/ui/components/dialog/Dialog.tsx @@ -1,9 +1,10 @@ import * as DialogPrimitive from "@radix-ui/react-dialog"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import type { ReactNode } from "react"; import React, { useState } from "react"; import classNames from "@calcom/lib/classNames"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { SVGComponent } from "@calcom/types/SVGComponent"; @@ -27,7 +28,7 @@ const enum DIALOG_STATE { export function Dialog(props: DialogProps) { const router = useRouter(); const pathname = usePathname(); - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const newSearchParams = new URLSearchParams(searchParams ?? undefined); const { children, name, ...dialogProps } = props; diff --git a/packages/ui/components/dialog/dialog.test.tsx b/packages/ui/components/dialog/dialog.test.tsx index 994b821e04..061bc386a5 100644 --- a/packages/ui/components/dialog/dialog.test.tsx +++ b/packages/ui/components/dialog/dialog.test.tsx @@ -3,6 +3,12 @@ import { vi } from "vitest"; import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "./Dialog"; +vi.mock("@calcom/lib/hooks/useCompatSearchParams", () => ({ + useCompatSearchParams() { + return new URLSearchParams(); + }, +})); + vi.mock("next/navigation", () => ({ usePathname() { return ""; diff --git a/packages/ui/components/form/wizard/WizardForm.tsx b/packages/ui/components/form/wizard/WizardForm.tsx index c532a25303..72fcf9aeb1 100644 --- a/packages/ui/components/form/wizard/WizardForm.tsx +++ b/packages/ui/components/form/wizard/WizardForm.tsx @@ -1,10 +1,11 @@ // eslint-disable-next-line no-restricted-imports import { noop } from "lodash"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import type { Dispatch, SetStateAction } from "react"; import { useEffect, useState } from "react"; import classNames from "@calcom/lib/classNames"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { Button, Steps } from "../../.."; @@ -28,7 +29,7 @@ function WizardForm(props: { finishLabel?: string; stepLabel?: React.ComponentProps["stepLabel"]; }) { - const searchParams = useSearchParams(); + const searchParams = useCompatSearchParams(); const { href, steps, nextLabel = "Next", finishLabel = "Finish", prevLabel = "Back", stepLabel } = props; const router = useRouter(); const step = parseInt((searchParams?.get("step") as string) || "1"); diff --git a/packages/ui/components/form/wizard/wizardForm.test.tsx b/packages/ui/components/form/wizard/wizardForm.test.tsx index 8cf7004d17..e52a5b5f8f 100644 --- a/packages/ui/components/form/wizard/wizardForm.test.tsx +++ b/packages/ui/components/form/wizard/wizardForm.test.tsx @@ -4,6 +4,12 @@ import { vi } from "vitest"; import WizardForm from "./WizardForm"; +vi.mock("@calcom/lib/hooks/useCompatSearchParams", () => ({ + useCompatSearchParams() { + return { get: vi.fn().mockReturnValue(currentStepNavigation) }; + }, +})); + vi.mock("next/navigation", () => ({ useRouter() { return { replace: vi.fn() }; diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 1b59d9ba80..70fe2276d9 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -72,6 +72,7 @@ const workspaces = packagedEmbedTestsOnly name: "@calcom/packages/lib/hooks", include: ["packages/lib/hooks/**/*.{test,spec}.{ts,js}"], environment: "jsdom", + setupFiles: [], }, }, ];