From 39cfe18ffe4596441f44ce6e562f00ba4076a810 Mon Sep 17 00:00:00 2001 From: Greg Pabian <35925521+grzpab@users.noreply.github.com> Date: Sat, 21 Oct 2023 01:47:05 +0200 Subject: [PATCH] chore: [app dir bootstrapping 4] check nullability of navigation hook return values (#12005) --- apps/web/components/booking/CancelBooking.tsx | 9 +++-- .../UsernameAvailability/PremiumTextfield.tsx | 4 +-- apps/web/lib/hooks/useIsBookingPage.ts | 8 ++--- apps/web/lib/hooks/useRouterQuery.ts | 16 +++++---- apps/web/pages/404.tsx | 13 ++++--- apps/web/pages/auth/login.tsx | 2 +- apps/web/pages/auth/oauth2/authorize.tsx | 2 +- apps/web/pages/auth/setup/index.tsx | 2 +- apps/web/pages/auth/verify.tsx | 2 +- apps/web/pages/booking/[uid].tsx | 2 +- apps/web/pages/event-types/index.tsx | 2 +- .../alby/components/AlbyPaymentComponent.tsx | 12 ++++++- packages/app-store/alby/pages/setup/index.tsx | 13 ++++--- .../routing-forms/components/FormActions.tsx | 2 +- .../pages/routing-link/[...appPages].tsx | 2 +- packages/features/apps/AdminAppsList.tsx | 2 +- .../OverlayCalendarContainer.tsx | 4 +-- .../features/bookings/Booker/utils/event.ts | 4 +-- .../components/event-meta/Members.tsx | 2 +- .../settings/other-team-members-view.tsx | 14 ++++---- .../settings/other-team-profile-view.tsx | 4 +-- .../ee/payments/components/Payment.tsx | 9 +++-- .../ee/users/pages/users-add-view.tsx | 5 ++- packages/features/embed/Embed.tsx | 9 ++--- .../insights/context/FiltersProvider.tsx | 35 ++++++++++--------- packages/features/shell/Shell.tsx | 17 +++++---- .../webhooks/pages/webhook-edit-view.tsx | 2 +- packages/lib/bookingSuccessRedirect.ts | 2 +- 28 files changed, 114 insertions(+), 86 deletions(-) diff --git a/apps/web/components/booking/CancelBooking.tsx b/apps/web/components/booking/CancelBooking.tsx index 76297831fa..693cd7ba34 100644 --- a/apps/web/components/booking/CancelBooking.tsx +++ b/apps/web/components/booking/CancelBooking.tsx @@ -1,4 +1,4 @@ -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { useCallback, useState } from "react"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -26,9 +26,6 @@ type Props = { }; export default function CancelBooking(props: Props) { - const pathname = usePathname(); - const searchParams = useSearchParams(); - const asPath = `${pathname}?${searchParams.toString()}`; const [cancellationReason, setCancellationReason] = useState(""); const { t } = useLocale(); const router = useRouter(); @@ -44,6 +41,7 @@ export default function CancelBooking(props: Props) { } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + return ( <> {error && ( @@ -100,7 +98,8 @@ export default function CancelBooking(props: Props) { }); if (res.status >= 200 && res.status < 300) { - router.replace(asPath); + // tested by apps/web/playwright/booking-pages.e2e.ts + router.refresh(); } else { setLoading(false); setError( diff --git a/apps/web/components/ui/UsernameAvailability/PremiumTextfield.tsx b/apps/web/components/ui/UsernameAvailability/PremiumTextfield.tsx index dbef95668d..fb33a0f899 100644 --- a/apps/web/components/ui/UsernameAvailability/PremiumTextfield.tsx +++ b/apps/web/components/ui/UsernameAvailability/PremiumTextfield.tsx @@ -222,9 +222,9 @@ const PremiumTextfield = (props: ICustomUsernameProps) => { onChange={(event) => { event.preventDefault(); // Reset payment status - const _searchParams = new URLSearchParams(searchParams); + const _searchParams = new URLSearchParams(searchParams ?? undefined); _searchParams.delete("paymentStatus"); - if (searchParams.toString() !== _searchParams.toString()) { + if (searchParams?.toString() !== _searchParams.toString()) { router.replace(`${pathname}?${_searchParams.toString()}`); } setInputUsernameValue(event.target.value); diff --git a/apps/web/lib/hooks/useIsBookingPage.ts b/apps/web/lib/hooks/useIsBookingPage.ts index 1e231e3f40..3f890bcedc 100644 --- a/apps/web/lib/hooks/useIsBookingPage.ts +++ b/apps/web/lib/hooks/useIsBookingPage.ts @@ -1,12 +1,12 @@ import { usePathname, useSearchParams } from "next/navigation"; -export default function useIsBookingPage() { +export default function useIsBookingPage(): boolean { const pathname = usePathname(); const isBookingPage = ["/booking/", "/cancel", "/reschedule"].some((route) => pathname?.startsWith(route)); const searchParams = useSearchParams(); - const userParam = searchParams.get("user"); - const teamParam = searchParams.get("team"); + const userParam = Boolean(searchParams?.get("user")); + const teamParam = Boolean(searchParams?.get("team")); - return !!(isBookingPage || userParam || teamParam); + return isBookingPage || userParam || teamParam; } diff --git a/apps/web/lib/hooks/useRouterQuery.ts b/apps/web/lib/hooks/useRouterQuery.ts index 56b321c1fa..3bd40e57b3 100644 --- a/apps/web/lib/hooks/useRouterQuery.ts +++ b/apps/web/lib/hooks/useRouterQuery.ts @@ -1,17 +1,21 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useCallback } from "react"; export default function useRouterQuery(name: T) { const searchParams = useSearchParams(); const pathname = usePathname(); const router = useRouter(); - const setQuery = (newValue: string | number | null | undefined) => { - const _searchParams = new URLSearchParams(searchParams); - _searchParams.set(name, newValue as string); - router.replace(`${pathname}?${_searchParams.toString()}`); - }; + const setQuery = useCallback( + (newValue: string | number | null | undefined) => { + const _searchParams = new URLSearchParams(searchParams ?? undefined); + _searchParams.set(name, newValue as string); + router.replace(`${pathname}?${_searchParams.toString()}`); + }, + [name, pathname, router, searchParams] + ); - return { [name]: searchParams.get(name), setQuery } as { + return { [name]: searchParams?.get(name), setQuery } as { [K in T]: string | undefined; } & { setQuery: typeof setQuery }; } diff --git a/apps/web/pages/404.tsx b/apps/web/pages/404.tsx index 6871c19630..fc23e64fd1 100644 --- a/apps/web/pages/404.tsx +++ b/apps/web/pages/404.tsx @@ -51,8 +51,8 @@ export default function Custom404() { const [url, setUrl] = useState(`${WEBSITE_URL}/signup`); useEffect(() => { const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(window.location.host); - const [routerUsername] = pathname?.replace("%20", "-").split(/[?#]/); - if (!isValidOrgDomain || !currentOrgDomain) { + const [routerUsername] = pathname?.replace("%20", "-").split(/[?#]/) ?? []; + if (routerUsername && (!isValidOrgDomain || !currentOrgDomain)) { const splitPath = routerUsername.split("/"); if (splitPath[1] === "team" && splitPath.length === 3) { // Accessing a non-existent team @@ -66,13 +66,12 @@ export default function Custom404() { setUrl(`${WEBSITE_URL}/signup?username=${routerUsername.replace("/", "")}`); } } else { - setUsername(currentOrgDomain); + setUsername(currentOrgDomain ?? ""); setCurrentPageType(pageType.ORG); setUrl( - `${WEBSITE_URL}/signup?callbackUrl=settings/organizations/new%3Fslug%3D${currentOrgDomain.replace( - "/", - "" - )}` + `${WEBSITE_URL}/signup?callbackUrl=settings/organizations/new%3Fslug%3D${ + currentOrgDomain?.replace("/", "") ?? "" + }` ); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/apps/web/pages/auth/login.tsx b/apps/web/pages/auth/login.tsx index d6b21a118f..780286c47a 100644 --- a/apps/web/pages/auth/login.tsx +++ b/apps/web/pages/auth/login.tsx @@ -83,7 +83,7 @@ inferSSRProps & WithNonceProps<{}>) { const telemetry = useTelemetry(); - let callbackUrl = searchParams.get("callbackUrl") || ""; + let callbackUrl = searchParams?.get("callbackUrl") || ""; if (/"\//.test(callbackUrl)) callbackUrl = callbackUrl.substring(1); diff --git a/apps/web/pages/auth/oauth2/authorize.tsx b/apps/web/pages/auth/oauth2/authorize.tsx index 6b2c276aac..e34635540c 100644 --- a/apps/web/pages/auth/oauth2/authorize.tsx +++ b/apps/web/pages/auth/oauth2/authorize.tsx @@ -22,7 +22,7 @@ export default function Authorize() { const state = searchParams?.get("state") as string; const scope = searchParams?.get("scope") as string; - const queryString = searchParams.toString(); + const queryString = searchParams?.toString(); const [selectedAccount, setSelectedAccount] = useState<{ value: string; label: string } | null>(); const scopes = scope ? scope.toString().split(",") : []; diff --git a/apps/web/pages/auth/setup/index.tsx b/apps/web/pages/auth/setup/index.tsx index 1fe903613f..4badbd1804 100644 --- a/apps/web/pages/auth/setup/index.tsx +++ b/apps/web/pages/auth/setup/index.tsx @@ -24,7 +24,7 @@ function useSetStep() { const searchParams = useSearchParams(); const pathname = usePathname(); const setStep = (newStep = 1) => { - const _searchParams = new URLSearchParams(searchParams); + const _searchParams = new URLSearchParams(searchParams ?? undefined); _searchParams.set("step", newStep.toString()); router.replace(`${pathname}?${_searchParams.toString()}`); }; diff --git a/apps/web/pages/auth/verify.tsx b/apps/web/pages/auth/verify.tsx index 8f6193d5ef..d0ce633d2f 100644 --- a/apps/web/pages/auth/verify.tsx +++ b/apps/web/pages/auth/verify.tsx @@ -164,7 +164,7 @@ export default function Verify() { e.preventDefault(); setSecondsLeft(30); // Update query params with t:timestamp, shallow: true doesn't re-render the page - const _searchParams = new URLSearchParams(searchParams.toString()); + const _searchParams = new URLSearchParams(searchParams?.toString()); _searchParams.set("t", `${Date.now()}`); router.replace(`${pathname}?${_searchParams.toString()}`); return await sendVerificationLogin(customer.email, customer.username); diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx index af7b779dc8..44eb293a1a 100644 --- a/apps/web/pages/booking/[uid].tsx +++ b/apps/web/pages/booking/[uid].tsx @@ -155,7 +155,7 @@ export default function Success(props: SuccessProps) { const [calculatedDuration, setCalculatedDuration] = useState(undefined); const { requiresLoginToUpdate } = props; function setIsCancellationMode(value: boolean) { - const _searchParams = new URLSearchParams(searchParams); + const _searchParams = new URLSearchParams(searchParams ?? undefined); if (value) { _searchParams.set("cancel", "true"); diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx index 27d825aa70..bd87521314 100644 --- a/apps/web/pages/event-types/index.tsx +++ b/apps/web/pages/event-types/index.tsx @@ -298,7 +298,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL // inject selection data into url for correct router history const openDuplicateModal = (eventType: EventType, group: EventTypeGroup) => { - const newSearchParams = new URLSearchParams(searchParams); + const newSearchParams = new URLSearchParams(searchParams ?? undefined); function setParamsIfDefined(key: string, value: string | number | boolean | null | undefined) { if (value) newSearchParams.set(key, value.toString()); if (value === null) newSearchParams.delete(key); diff --git a/packages/app-store/alby/components/AlbyPaymentComponent.tsx b/packages/app-store/alby/components/AlbyPaymentComponent.tsx index a4977b64cd..34599a9800 100644 --- a/packages/app-store/alby/components/AlbyPaymentComponent.tsx +++ b/packages/app-store/alby/components/AlbyPaymentComponent.tsx @@ -134,7 +134,15 @@ function PaymentChecker(props: PaymentCheckerProps) { const bookingSuccessRedirect = useBookingSuccessRedirect(); const utils = trpc.useContext(); const { t } = useLocale(); + useEffect(() => { + if (searchParams === null) { + return; + } + + // use closure to ensure non-nullability + const sp = searchParams; + const interval = setInterval(() => { (async () => { if (props.booking.status === "ACCEPTED") { @@ -153,7 +161,7 @@ function PaymentChecker(props: PaymentCheckerProps) { location: string; } = { uid: props.booking.uid, - email: searchParams.get("email"), + email: sp.get("email"), location: t("web_conferencing_details_to_follow"), }; @@ -165,6 +173,7 @@ function PaymentChecker(props: PaymentCheckerProps) { } })(); }, 1000); + return () => clearInterval(interval); }, [ bookingSuccessRedirect, @@ -178,5 +187,6 @@ function PaymentChecker(props: PaymentCheckerProps) { t, utils.viewer.bookings, ]); + return null; } diff --git a/packages/app-store/alby/pages/setup/index.tsx b/packages/app-store/alby/pages/setup/index.tsx index 9017af73ec..fdd8403b03 100644 --- a/packages/app-store/alby/pages/setup/index.tsx +++ b/packages/app-store/alby/pages/setup/index.tsx @@ -30,15 +30,20 @@ export default function AlbySetup(props: IAlbySetupProps) { function AlbySetupCallback() { const [error, setError] = useState(null); - const params = useSearchParams(); + const searchParams = useSearchParams(); + useEffect(() => { + if (!searchParams) { + return; + } + if (!window.opener) { setError("Something went wrong. Opener not available. Please contact support@getalby.com"); return; } - const code = params.get("code"); - const error = params.get("error"); + const code = searchParams?.get("code"); + const error = searchParams?.get("error"); if (!code) { setError("declined"); @@ -54,7 +59,7 @@ function AlbySetupCallback() { payload: { code }, }); window.close(); - }, []); + }, [searchParams]); return (
diff --git a/packages/app-store/routing-forms/components/FormActions.tsx b/packages/app-store/routing-forms/components/FormActions.tsx index 17a2932d1c..6c96a221eb 100644 --- a/packages/app-store/routing-forms/components/FormActions.tsx +++ b/packages/app-store/routing-forms/components/FormActions.tsx @@ -48,7 +48,7 @@ export const useOpenModal = () => { const pathname = usePathname(); const searchParams = useSearchParams(); const openModal = (option: z.infer) => { - const newQuery = new URLSearchParams(searchParams); + const newQuery = new URLSearchParams(searchParams ?? undefined); newQuery.set("dialog", "new-form"); Object.keys(option).forEach((key) => { newQuery.set(key, option[key as keyof typeof option] || ""); 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 8ff57e60ea..18b400a72d 100644 --- a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx @@ -301,7 +301,7 @@ const usePrefilledResponse = (form: Props["form"]) => { // Prefill the form from query params form.fields?.forEach((field) => { - const valuesFromQuery = searchParams?.getAll(getFieldIdentifier(field)).filter(Boolean); + const valuesFromQuery = searchParams?.getAll(getFieldIdentifier(field)).filter(Boolean) ?? []; // We only want to keep arrays if the field is a multi-select const value = valuesFromQuery.length > 1 ? valuesFromQuery : valuesFromQuery[0]; diff --git a/packages/features/apps/AdminAppsList.tsx b/packages/features/apps/AdminAppsList.tsx index 5991687276..5c96da95bd 100644 --- a/packages/features/apps/AdminAppsList.tsx +++ b/packages/features/apps/AdminAppsList.tsx @@ -268,7 +268,7 @@ interface EditModalState extends Pick { const AdminAppsListContainer = () => { const searchParams = useSearchParams(); const { t } = useLocale(); - const category = searchParams.get("category") || AppCategories.calendar; + const category = searchParams?.get("category") || AppCategories.calendar; const { data: apps, isLoading } = trpc.viewer.appsRouter.listLocal.useQuery( { category }, diff --git a/packages/features/bookings/Booker/components/OverlayCalendar/OverlayCalendarContainer.tsx b/packages/features/bookings/Booker/components/OverlayCalendar/OverlayCalendarContainer.tsx index a4e52447f5..1e7e479bca 100644 --- a/packages/features/bookings/Booker/components/OverlayCalendar/OverlayCalendarContainer.tsx +++ b/packages/features/bookings/Booker/components/OverlayCalendar/OverlayCalendarContainer.tsx @@ -38,7 +38,7 @@ function OverlayCalendarSwitch({ enabled }: OverlayCalendarSwitchProps) { // Toggle query param for overlay calendar const toggleOverlayCalendarQueryParam = useCallback( (state: boolean) => { - const current = new URLSearchParams(Array.from(searchParams.entries())); + const current = new URLSearchParams(Array.from(searchParams?.entries() ?? [])); if (state) { current.set("overlayCalendar", "true"); localStorage.setItem("overlayCalendarSwitchDefault", "true"); @@ -121,7 +121,7 @@ export function OverlayCalendarContainer() { const { data: session, status: sessionStatus } = useSession(); const setOverlayBusyDates = useOverlayCalendarStore((state) => state.setOverlayBusyDates); const switchEnabled = - searchParams.get("overlayCalendar") === "true" || + searchParams?.get("overlayCalendar") === "true" || localStorage.getItem("overlayCalendarSwitchDefault") === "true"; const selectedDate = useBookerStore((state) => state.selectedDate); diff --git a/packages/features/bookings/Booker/utils/event.ts b/packages/features/bookings/Booker/utils/event.ts index 519861837f..b32440197c 100644 --- a/packages/features/bookings/Booker/utils/event.ts +++ b/packages/features/bookings/Booker/utils/event.ts @@ -62,7 +62,7 @@ export const useScheduleForEvent = ({ shallow ); const searchParams = useSearchParams(); - const rescheduleUid = searchParams.get("rescheduleUid"); + const rescheduleUid = searchParams?.get("rescheduleUid"); const pathname = usePathname(); @@ -78,6 +78,6 @@ export const useScheduleForEvent = ({ rescheduleUid, month: monthFromStore ?? month, duration: durationFromStore ?? duration, - isTeamEvent: pathname.indexOf("/team/") !== -1 || isTeam, + isTeamEvent: pathname?.indexOf("/team/") !== -1 || isTeam, }); }; diff --git a/packages/features/bookings/components/event-meta/Members.tsx b/packages/features/bookings/components/event-meta/Members.tsx index 85d8f22d2c..3101892eaa 100644 --- a/packages/features/bookings/components/event-meta/Members.tsx +++ b/packages/features/bookings/components/event-meta/Members.tsx @@ -61,7 +61,7 @@ export const EventMembers = ({ schedulingType, users, profile, entity }: EventMe image: "logo" in profile && profile.logo ? `${profile.logo}` : undefined, alt: profile.name || undefined, href: profile.username - ? `${CAL_URL}${pathname.indexOf("/team/") !== -1 ? "/team" : ""}/${profile.username}` + ? `${CAL_URL}${pathname?.indexOf("/team/") !== -1 ? "/team" : ""}/${profile.username}` : undefined, }); diff --git a/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx b/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx index 0e026e8127..6575ddda81 100644 --- a/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx +++ b/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx @@ -58,7 +58,7 @@ const MembersView = () => { const { t, i18n } = useLocale(); const router = useRouter(); const searchParams = useSearchParams(); - const teamId = Number(searchParams.get("id")); + const teamId = Number(searchParams?.get("id")); const session = useSession(); const utils = trpc.useContext(); const [offset, setOffset] = useState(1); @@ -74,6 +74,7 @@ const MembersView = () => { const { data: team, isLoading: isTeamLoading } = trpc.viewer.organizations.getOtherTeam.useQuery( { teamId }, { + enabled: !Number.isNaN(teamId), onError: () => { router.push("/settings"); }, @@ -86,13 +87,14 @@ const MembersView = () => { distinctUser: true, }, { - enabled: searchParams !== null, + enabled: !Number.isNaN(teamId), } ); const { data: membersFetch, isLoading: isLoadingMembers } = trpc.viewer.organizations.listOtherTeamMembers.useQuery( { teamId, limit, offset: (offset - 1) * limit }, { + enabled: !Number.isNaN(teamId), onError: () => { router.push("/settings"); }, @@ -101,12 +103,8 @@ const MembersView = () => { useEffect(() => { if (membersFetch) { - if (membersFetch.length < limit) { - setLoadMore(false); - } else { - setLoadMore(true); - } - setMembers(members.concat(membersFetch)); + setLoadMore(membersFetch.length >= limit); + setMembers((m) => m.concat(membersFetch)); } }, [membersFetch]); diff --git a/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx b/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx index 1d97111bbc..1b04688418 100644 --- a/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx +++ b/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx @@ -77,11 +77,11 @@ const OtherTeamProfileView = () => { resolver: zodResolver(teamProfileFormSchema), }); const searchParams = useSearchParams(); - const teamId = Number(searchParams.get("id")); + const teamId = Number(searchParams?.get("id")); const { data: team, isLoading } = trpc.viewer.organizations.getOtherTeam.useQuery( { teamId: teamId }, { - enabled: !!teamId, + enabled: !Number.isNaN(teamId), onError: () => { router.push("/settings"); }, diff --git a/packages/features/ee/payments/components/Payment.tsx b/packages/features/ee/payments/components/Payment.tsx index be4f3a5e69..4311e9036c 100644 --- a/packages/features/ee/payments/components/Payment.tsx +++ b/packages/features/ee/payments/components/Payment.tsx @@ -81,12 +81,17 @@ const PaymentForm = (props: Props) => { const handleSubmit = async (ev: SyntheticEvent) => { ev.preventDefault(); - if (!stripe || !elements) return; + if (!stripe || !elements || searchParams === null) { + return; + } + setState({ status: "processing" }); let payload; const params: { - [k: string]: any; + uid: string; + email: string | null; + location?: string; } = { uid: props.booking.uid, email: searchParams.get("email"), diff --git a/packages/features/ee/users/pages/users-add-view.tsx b/packages/features/ee/users/pages/users-add-view.tsx index 052e546443..30ffc93ae0 100644 --- a/packages/features/ee/users/pages/users-add-view.tsx +++ b/packages/features/ee/users/pages/users-add-view.tsx @@ -17,7 +17,10 @@ const UsersAddView = () => { onSuccess: async () => { showToast("User added successfully", "success"); await utils.viewer.users.list.invalidate(); - router.replace(pathname?.replace("/add", "")); + + if (pathname !== null) { + router.replace(pathname.replace("/add", "")); + } }, onError: (err) => { console.error(err.message); diff --git a/packages/features/embed/Embed.tsx b/packages/features/embed/Embed.tsx index 8ed8abb4dd..69df6baaf6 100644 --- a/packages/features/embed/Embed.tsx +++ b/packages/features/embed/Embed.tsx @@ -520,18 +520,19 @@ const EmbedTypeCodeAndPreviewDialogContent = ({ (state) => [state.month, state.selectedDatesAndTimes], shallow ); - const eventId = searchParams.get("eventId"); + const eventId = searchParams?.get("eventId"); + const parsedEventId = parseInt(eventId ?? "", 10); const calLink = decodeURIComponent(embedUrl); const { data: eventTypeData } = trpc.viewer.eventTypes.get.useQuery( - { id: parseInt(eventId as string) }, - { enabled: !!eventId && embedType === "email", refetchOnWindowFocus: false } + { id: parsedEventId }, + { enabled: !Number.isNaN(parsedEventId) && embedType === "email", refetchOnWindowFocus: false } ); const s = (href: string) => { const _searchParams = new URLSearchParams(searchParams); const [a, b] = href.split("="); _searchParams.set(a, b); - return `${pathname?.split("?")[0]}?${_searchParams.toString()}`; + return `${pathname?.split("?")[0] ?? ""}?${_searchParams.toString()}`; }; const parsedTabs = tabs.map((t) => ({ ...t, href: s(t.href) })); const embedCodeRefs: Record<(typeof tabs)[0]["name"], RefObject> = {}; diff --git a/packages/features/insights/context/FiltersProvider.tsx b/packages/features/insights/context/FiltersProvider.tsx index dcebb2a777..2eec6e91b3 100644 --- a/packages/features/insights/context/FiltersProvider.tsx +++ b/packages/features/insights/context/FiltersProvider.tsx @@ -8,21 +8,22 @@ import { trpc } from "@calcom/trpc"; import type { FilterContextType } from "./provider"; import { FilterProvider } from "./provider"; +const querySchema = z.object({ + startTime: z.string().nullable(), + endTime: z.string().nullable(), + teamId: z.coerce.number().nullable(), + userId: z.coerce.number().nullable(), + memberUserId: z.coerce.number().nullable(), + eventTypeId: z.coerce.number().nullable(), + filter: z.enum(["event-type", "user"]).nullable(), +}); + export function FiltersProvider({ children }: { children: React.ReactNode }) { // searchParams to get initial values from query params const utils = trpc.useContext(); const searchParams = useSearchParams(); const router = useRouter(); const pathname = usePathname(); - const querySchema = z.object({ - startTime: z.string().nullable(), - endTime: z.string().nullable(), - teamId: z.coerce.number().nullable(), - userId: z.coerce.number().nullable(), - memberUserId: z.coerce.number().nullable(), - eventTypeId: z.coerce.number().nullable(), - filter: z.enum(["event-type", "user"]).nullable(), - }); let startTimeParsed, endTimeParsed, @@ -33,13 +34,13 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) { memberUserIdParsed; const safe = querySchema.safeParse({ - startTime: searchParams.get("startTime"), - endTime: searchParams.get("endTime"), - teamId: searchParams.get("teamId"), - userId: searchParams.get("userId"), - eventTypeId: searchParams.get("eventTypeId"), - filter: searchParams.get("filter"), - memberUserId: searchParams.get("memberUserId"), + startTime: searchParams?.get("startTime") ?? null, + endTime: searchParams?.get("endTime") ?? null, + teamId: searchParams?.get("teamId") ?? null, + userId: searchParams?.get("userId") ?? null, + eventTypeId: searchParams?.get("eventTypeId") ?? null, + filter: searchParams?.get("filter") ?? null, + memberUserId: searchParams?.get("memberUserId") ?? null, }); if (!safe.success) { @@ -119,7 +120,7 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) { initialConfig, } = newConfigFilters; const [startTime, endTime] = dateRange || [null, null]; - const newSearchParams = new URLSearchParams(searchParams.toString()); + const newSearchParams = new URLSearchParams(searchParams?.toString() ?? undefined); function setParamsIfDefined(key: string, value: string | number | boolean | null | undefined) { if (value !== undefined && value !== null) newSearchParams.set(key, value.toString()); } diff --git a/packages/features/shell/Shell.tsx b/packages/features/shell/Shell.tsx index 22eb8685cd..af669bd647 100644 --- a/packages/features/shell/Shell.tsx +++ b/packages/features/shell/Shell.tsx @@ -509,7 +509,7 @@ export type NavigationItemType = { }: { item: Pick; isChild?: boolean; - pathname: string; + pathname: string | null; }) => boolean; }; @@ -527,7 +527,7 @@ const navigation: NavigationItemType[] = [ href: "/bookings/upcoming", icon: Calendar, badge: , - isCurrent: ({ pathname }) => pathname?.startsWith("/bookings"), + isCurrent: ({ pathname }) => pathname?.startsWith("/bookings") ?? false, }, { name: "availability", @@ -547,7 +547,7 @@ const navigation: NavigationItemType[] = [ icon: Grid, isCurrent: ({ pathname: path, item }) => { // During Server rendering path is /v2/apps but on client it becomes /apps(weird..) - return path?.startsWith(item.href) && !path?.includes("routing-forms/"); + return (path?.startsWith(item.href) ?? false) && !(path?.includes("routing-forms/") ?? false); }, child: [ { @@ -556,7 +556,9 @@ const navigation: NavigationItemType[] = [ isCurrent: ({ pathname: path, item }) => { // During Server rendering path is /v2/apps but on client it becomes /apps(weird..) return ( - path?.startsWith(item.href) && !path?.includes("routing-forms/") && !path?.includes("/installed") + (path?.startsWith(item.href) ?? false) && + !(path?.includes("routing-forms/") ?? false) && + !(path?.includes("/installed") ?? false) ); }, }, @@ -564,7 +566,8 @@ const navigation: NavigationItemType[] = [ name: "installed_apps", href: "/apps/installed/calendar", isCurrent: ({ pathname: path }) => - path?.startsWith("/apps/installed/") || path?.startsWith("/v2/apps/installed/"), + (path?.startsWith("/apps/installed/") ?? false) || + (path?.startsWith("/v2/apps/installed/") ?? false), }, ], }, @@ -577,7 +580,7 @@ const navigation: NavigationItemType[] = [ name: "Routing Forms", href: "/apps/routing-forms/forms", icon: FileText, - isCurrent: ({ pathname }) => pathname?.startsWith("/apps/routing-forms/"), + isCurrent: ({ pathname }) => pathname?.startsWith("/apps/routing-forms/") ?? false, }, { name: "workflows", @@ -631,7 +634,7 @@ function useShouldDisplayNavigationItem(item: NavigationItemType) { } const defaultIsCurrent: NavigationItemType["isCurrent"] = ({ isChild, item, pathname }) => { - return isChild ? item.href === pathname : item.href ? pathname?.startsWith(item.href) : false; + return isChild ? item.href === pathname : item.href ? pathname?.startsWith(item.href) ?? false : false; }; const NavigationItem: React.FC<{ diff --git a/packages/features/webhooks/pages/webhook-edit-view.tsx b/packages/features/webhooks/pages/webhook-edit-view.tsx index 7b3c62a857..e44ffeb5e6 100644 --- a/packages/features/webhooks/pages/webhook-edit-view.tsx +++ b/packages/features/webhooks/pages/webhook-edit-view.tsx @@ -12,7 +12,7 @@ import { subscriberUrlReserved } from "../lib/subscriberUrlReserved"; const EditWebhook = () => { const searchParams = useSearchParams(); - const id = searchParams.get("id"); + const id = searchParams?.get("id"); if (!id) return ; diff --git a/packages/lib/bookingSuccessRedirect.ts b/packages/lib/bookingSuccessRedirect.ts index 303f4fca4e..b1e3c6a8a0 100644 --- a/packages/lib/bookingSuccessRedirect.ts +++ b/packages/lib/bookingSuccessRedirect.ts @@ -60,7 +60,7 @@ export const useBookingSuccessRedirect = () => { ...query, ...bookingExtraParams, }, - searchParams, + searchParams: searchParams ?? undefined, }); window.parent.location.href = `${url.toString()}?${newSearchParams.toString()}`; return;