From 210bc9fc8c6aba4145f5fdf3adf49aee6f92f926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20L=C3=B3pez?= Date: Thu, 25 Jan 2024 17:31:08 -0700 Subject: [PATCH] chore: Proposal for new module based file structure (#13396) --- .../app/future/booking/[uid]/embed/page.tsx | 5 +- apps/web/app/future/booking/[uid]/page.tsx | 5 +- .../web/app/future/bookings/[status]/page.tsx | 7 +- .../web/modules/bookings/lib/validStatuses.ts | 1 + .../bookings-listing-view.getStaticProps.tsx} | 2 +- .../bookings/views/bookings-listing-view.tsx | 252 +++++++++++++++++ ...okings-single-view.getServerSideProps.tsx} | 4 + .../bookings/views/bookings-single-view.tsx} | 8 +- apps/web/pages/booking/[uid]/embed.tsx | 7 +- apps/web/pages/booking/[uid]/index.tsx | 2 + apps/web/pages/bookings/[status].tsx | 263 +----------------- 11 files changed, 285 insertions(+), 271 deletions(-) create mode 100644 apps/web/modules/bookings/lib/validStatuses.ts rename apps/web/{lib/bookings/[status]/getStaticProps.tsx => modules/bookings/views/bookings-listing-view.getStaticProps.tsx} (84%) create mode 100644 apps/web/modules/bookings/views/bookings-listing-view.tsx rename apps/web/{lib/booking/[uid]/getServerSideProps.tsx => modules/bookings/views/bookings-single-view.getServerSideProps.tsx} (97%) rename apps/web/{pages/booking/[uid].tsx => modules/bookings/views/bookings-single-view.tsx} (99%) create mode 100644 apps/web/pages/booking/[uid]/index.tsx diff --git a/apps/web/app/future/booking/[uid]/embed/page.tsx b/apps/web/app/future/booking/[uid]/embed/page.tsx index 18510597d7..7a314fc547 100644 --- a/apps/web/app/future/booking/[uid]/embed/page.tsx +++ b/apps/web/app/future/booking/[uid]/embed/page.tsx @@ -1,9 +1,10 @@ -import { type PageProps, getServerSideProps } from "@pages/booking/[uid]"; -import OldPage from "@pages/booking/[uid]/embed"; import { withAppDirSsr } from "app/WithAppDirSsr"; import withEmbedSsrAppDir from "app/WithEmbedSSR"; import { WithLayout } from "app/layoutHOC"; +import OldPage from "~/bookings/views/bookings-single-view"; +import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps"; + const getData = withAppDirSsr(getServerSideProps); const getEmbedData = withEmbedSsrAppDir(getData); diff --git a/apps/web/app/future/booking/[uid]/page.tsx b/apps/web/app/future/booking/[uid]/page.tsx index a8359a43d1..9b44dc7899 100644 --- a/apps/web/app/future/booking/[uid]/page.tsx +++ b/apps/web/app/future/booking/[uid]/page.tsx @@ -1,4 +1,3 @@ -import OldPage, { type PageProps } from "@pages/booking/[uid]"; import { withAppDirSsr } from "app/WithAppDirSsr"; import type { Params, SearchParams } from "app/_types"; import { _generateMetadata } from "app/_utils"; @@ -8,9 +7,11 @@ import { cookies, headers } from "next/headers"; import { BookingStatus } from "@calcom/prisma/enums"; -import { getServerSideProps } from "@lib/booking/[uid]/getServerSideProps"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import OldPage from "~/bookings/views/bookings-single-view"; +import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps"; + export const generateMetadata = async ({ params, searchParams, diff --git a/apps/web/app/future/bookings/[status]/page.tsx b/apps/web/app/future/bookings/[status]/page.tsx index f821d7a6a1..80b377b458 100644 --- a/apps/web/app/future/bookings/[status]/page.tsx +++ b/apps/web/app/future/bookings/[status]/page.tsx @@ -1,4 +1,3 @@ -import Page from "@pages/bookings/[status]"; import { withAppDirSsg } from "app/WithAppDirSsg"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; @@ -7,9 +6,9 @@ import type { InferGetStaticPropsType } from "next"; import { getLayout } from "@calcom/features/MainLayoutAppDir"; import { APP_NAME } from "@calcom/lib/constants"; -import { getStaticProps } from "@lib/bookings/[status]/getStaticProps"; - -const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const; +import { validStatuses } from "~/bookings/lib/validStatuses"; +import Page from "~/bookings/views/bookings-listing-view"; +import { getStaticProps } from "~/bookings/views/bookings-listing-view.getStaticProps"; type Y = InferGetStaticPropsType; const getData = withAppDirSsg(getStaticProps); diff --git a/apps/web/modules/bookings/lib/validStatuses.ts b/apps/web/modules/bookings/lib/validStatuses.ts new file mode 100644 index 0000000000..6315f84d10 --- /dev/null +++ b/apps/web/modules/bookings/lib/validStatuses.ts @@ -0,0 +1 @@ +export const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const; diff --git a/apps/web/lib/bookings/[status]/getStaticProps.tsx b/apps/web/modules/bookings/views/bookings-listing-view.getStaticProps.tsx similarity index 84% rename from apps/web/lib/bookings/[status]/getStaticProps.tsx rename to apps/web/modules/bookings/views/bookings-listing-view.getStaticProps.tsx index 0415a9a66c..cb35c2a647 100644 --- a/apps/web/lib/bookings/[status]/getStaticProps.tsx +++ b/apps/web/modules/bookings/views/bookings-listing-view.getStaticProps.tsx @@ -3,7 +3,7 @@ import { z } from "zod"; import { getTranslations } from "@server/lib/getTranslations"; -const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const; +import { validStatuses } from "~/bookings/lib/validStatuses"; const querySchema = z.object({ status: z.enum(validStatuses), diff --git a/apps/web/modules/bookings/views/bookings-listing-view.tsx b/apps/web/modules/bookings/views/bookings-listing-view.tsx new file mode 100644 index 0000000000..b8adf4c3e3 --- /dev/null +++ b/apps/web/modules/bookings/views/bookings-listing-view.tsx @@ -0,0 +1,252 @@ +"use client"; + +import { useAutoAnimate } from "@formkit/auto-animate/react"; +import { Fragment, useState } from "react"; +import { z } from "zod"; + +import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components"; +import dayjs from "@calcom/dayjs"; +import { FilterToggle } from "@calcom/features/bookings/components/FilterToggle"; +import { FiltersContainer } from "@calcom/features/bookings/components/FiltersContainer"; +import type { filterQuerySchema } from "@calcom/features/bookings/lib/useFilterQuery"; +import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery"; +import { ShellMain } from "@calcom/features/shell/Shell"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; +import type { RouterOutputs } from "@calcom/trpc/react"; +import { trpc } from "@calcom/trpc/react"; +import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui"; +import { Alert, Button, EmptyScreen, HorizontalTabs } from "@calcom/ui"; +import { Calendar } from "@calcom/ui/components/icon"; + +import { useInViewObserver } from "@lib/hooks/useInViewObserver"; +import useMeQuery from "@lib/hooks/useMeQuery"; + +import BookingListItem from "@components/booking/BookingListItem"; +import SkeletonLoader from "@components/booking/SkeletonLoader"; + +import { validStatuses } from "~/bookings/lib/validStatuses"; + +type BookingListingStatus = z.infer>["status"]; +type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0]; + +type RecurringInfo = { + recurringEventId: string | null; + count: number; + firstDate: Date | null; + bookings: { [key: string]: Date[] }; +}; + +const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [ + { + name: "upcoming", + href: "/bookings/upcoming", + }, + { + name: "unconfirmed", + href: "/bookings/unconfirmed", + }, + { + name: "recurring", + href: "/bookings/recurring", + }, + { + name: "past", + href: "/bookings/past", + }, + { + name: "cancelled", + href: "/bookings/cancelled", + }, +]; + +const descriptionByStatus: Record, string> = { + upcoming: "upcoming_bookings", + recurring: "recurring_bookings", + past: "past_bookings", + cancelled: "cancelled_bookings", + unconfirmed: "unconfirmed_bookings", +}; + +const querySchema = z.object({ + status: z.enum(validStatuses), +}); + +export default function Bookings() { + const params = useParamsWithFallback(); + const { data: filterQuery } = useFilterQuery(); + const { status } = params ? querySchema.parse(params) : { status: "upcoming" as const }; + const { t } = useLocale(); + const user = useMeQuery().data; + const [isFiltersVisible, setIsFiltersVisible] = useState(false); + + const query = trpc.viewer.bookings.get.useInfiniteQuery( + { + limit: 10, + filters: { + ...filterQuery, + status: filterQuery.status ?? status, + }, + }, + { + // first render has status `undefined` + enabled: true, + getNextPageParam: (lastPage) => lastPage.nextCursor, + } + ); + + // Animate page (tab) transitions to look smoothing + + const buttonInView = useInViewObserver(() => { + if (!query.isFetching && query.hasNextPage && query.status === "success") { + query.fetchNextPage(); + } + }); + + const isEmpty = !query.data?.pages[0]?.bookings.length; + + const shownBookings: Record = {}; + const filterBookings = (booking: BookingOutput) => { + if (status === "recurring" || status == "unconfirmed" || status === "cancelled") { + if (!booking.recurringEventId) { + return true; + } + if ( + shownBookings[booking.recurringEventId] !== undefined && + shownBookings[booking.recurringEventId].length > 0 + ) { + shownBookings[booking.recurringEventId].push(booking); + return false; + } + shownBookings[booking.recurringEventId] = [booking]; + } else if (status === "upcoming") { + return ( + dayjs(booking.startTime).tz(user?.timeZone).format("YYYY-MM-DD") !== + dayjs().tz(user?.timeZone).format("YYYY-MM-DD") + ); + } + return true; + }; + + let recurringInfoToday: RecurringInfo | undefined; + + const bookingsToday = + query.data?.pages.map((page) => + page.bookings.filter((booking: BookingOutput) => { + recurringInfoToday = page.recurringInfo.find( + (info) => info.recurringEventId === booking.recurringEventId + ); + + return ( + dayjs(booking.startTime).tz(user?.timeZone).format("YYYY-MM-DD") === + dayjs().tz(user?.timeZone).format("YYYY-MM-DD") + ); + }) + )[0] || []; + + const [animationParentRef] = useAutoAnimate(); + + return ( + +
+
+ + +
+ +
+
+ {query.status === "error" && ( + + )} + {(query.status === "pending" || query.isPaused) && } + {query.status === "success" && !isEmpty && ( + <> + {!!bookingsToday.length && status === "upcoming" && ( +
+ +

{t("today")}

+
+ + + + {bookingsToday.map((booking: BookingOutput) => ( + + ))} + + +
+
+
+ )} +
+
+ + + {query.data.pages.map((page, index) => ( + + {page.bookings.filter(filterBookings).map((booking: BookingOutput) => { + const recurringInfo = page.recurringInfo.find( + (info) => info.recurringEventId === booking.recurringEventId + ); + return ( + + ); + })} + + ))} + +
+
+
+ +
+
+ + )} + {query.status === "success" && isEmpty && ( +
+ +
+ )} +
+
+
+
+ ); +} diff --git a/apps/web/lib/booking/[uid]/getServerSideProps.tsx b/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx similarity index 97% rename from apps/web/lib/booking/[uid]/getServerSideProps.tsx rename to apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx index e10af55fb1..62a6d4dade 100644 --- a/apps/web/lib/booking/[uid]/getServerSideProps.tsx +++ b/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx @@ -9,6 +9,8 @@ import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUi import prisma from "@calcom/prisma"; import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; +import type { inferSSRProps } from "@lib/types/inferSSRProps"; + import { ssrInit } from "@server/lib/ssr"; const stringToBoolean = z @@ -29,6 +31,8 @@ const querySchema = z.object({ seatReferenceUid: z.string().optional(), }); +export type PageProps = inferSSRProps; + export async function getServerSideProps(context: GetServerSidePropsContext) { // this is needed to prevent bundling of lib/booking to the client bundle // usually functions that are used in getServerSideProps are tree shaken from client bundle diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/modules/bookings/views/bookings-single-view.tsx similarity index 99% rename from apps/web/pages/booking/[uid].tsx rename to apps/web/modules/bookings/views/bookings-single-view.tsx index 0ab4c68487..b9298c8c1b 100644 --- a/apps/web/pages/booking/[uid].tsx +++ b/apps/web/modules/bookings/views/bookings-single-view.tsx @@ -44,17 +44,13 @@ import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; import { Alert, Badge, Button, EmailInput, HeadSeo, useCalcomTheme } from "@calcom/ui"; import { AlertCircle, Calendar, Check, ChevronLeft, ExternalLink, X } from "@calcom/ui/components/icon"; -import { getServerSideProps } from "@lib/booking/[uid]/getServerSideProps"; import { timeZone } from "@lib/clock"; -import type { inferSSRProps } from "@lib/types/inferSSRProps"; import PageWrapper from "@components/PageWrapper"; import CancelBooking from "@components/booking/CancelBooking"; import EventReservationSchema from "@components/schemas/EventReservationSchema"; -export { getServerSideProps }; - -export type PageProps = inferSSRProps; +import type { PageProps } from "./bookings-single-view.getServerSideProps"; const stringToBoolean = z .string() @@ -837,7 +833,7 @@ type RecurringBookingsProps = { tz: string; }; -export function RecurringBookings({ +function RecurringBookings({ eventType, recurringBookings, duration, diff --git a/apps/web/pages/booking/[uid]/embed.tsx b/apps/web/pages/booking/[uid]/embed.tsx index 3e004ab3e6..7c4dbc8f94 100644 --- a/apps/web/pages/booking/[uid]/embed.tsx +++ b/apps/web/pages/booking/[uid]/embed.tsx @@ -1,8 +1,7 @@ -"use client"; - -import { getServerSideProps as _getServerSideProps } from "@lib/booking/[uid]/getServerSideProps"; import withEmbedSsr from "@lib/withEmbedSsr"; -export { default } from "../[uid]"; +import { getServerSideProps as _getServerSideProps } from "~/bookings/views/bookings-single-view.getServerSideProps"; + +export { default } from "~/bookings/views/bookings-single-view"; export const getServerSideProps = withEmbedSsr(_getServerSideProps); diff --git a/apps/web/pages/booking/[uid]/index.tsx b/apps/web/pages/booking/[uid]/index.tsx new file mode 100644 index 0000000000..3446d77b84 --- /dev/null +++ b/apps/web/pages/booking/[uid]/index.tsx @@ -0,0 +1,2 @@ +export { default } from "~/bookings/views/bookings-single-view"; +export { getServerSideProps } from "~/bookings/views/bookings-single-view.getServerSideProps"; diff --git a/apps/web/pages/bookings/[status].tsx b/apps/web/pages/bookings/[status].tsx index 71aa4951db..3e23057ee2 100644 --- a/apps/web/pages/bookings/[status].tsx +++ b/apps/web/pages/bookings/[status].tsx @@ -1,263 +1,22 @@ -"use client"; - -import { useAutoAnimate } from "@formkit/auto-animate/react"; import type { GetStaticPaths } from "next"; -import { Fragment, useState } from "react"; -import React from "react"; -import { z } from "zod"; -import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components"; -import dayjs from "@calcom/dayjs"; import { getLayout } from "@calcom/features/MainLayout"; -import { FilterToggle } from "@calcom/features/bookings/components/FilterToggle"; -import { FiltersContainer } from "@calcom/features/bookings/components/FiltersContainer"; -import type { filterQuerySchema } from "@calcom/features/bookings/lib/useFilterQuery"; -import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery"; -import { ShellMain } from "@calcom/features/shell/Shell"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; -import type { RouterOutputs } from "@calcom/trpc/react"; -import { trpc } from "@calcom/trpc/react"; -import { HorizontalTabs } from "@calcom/ui"; -import type { VerticalTabItemProps, HorizontalTabItemProps } from "@calcom/ui"; -import { Alert, Button, EmptyScreen } from "@calcom/ui"; -import { Calendar } from "@calcom/ui/components/icon"; - -import { getStaticProps } from "@lib/bookings/[status]/getStaticProps"; -import { useInViewObserver } from "@lib/hooks/useInViewObserver"; -import useMeQuery from "@lib/hooks/useMeQuery"; import PageWrapper from "@components/PageWrapper"; -import BookingListItem from "@components/booking/BookingListItem"; -import SkeletonLoader from "@components/booking/SkeletonLoader"; -type BookingListingStatus = z.infer>["status"]; -type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0]; +import { validStatuses } from "~/bookings/lib/validStatuses"; +import BookingsListingView from "~/bookings/views/bookings-listing-view"; -type RecurringInfo = { - recurringEventId: string | null; - count: number; - firstDate: Date | null; - bookings: { [key: string]: Date[] }; -}; +export { getStaticProps } from "~/bookings/views/bookings-listing-view.getStaticProps"; -const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [ - { - name: "upcoming", - href: "/bookings/upcoming", - }, - { - name: "unconfirmed", - href: "/bookings/unconfirmed", - }, - { - name: "recurring", - href: "/bookings/recurring", - }, - { - name: "past", - href: "/bookings/past", - }, - { - name: "cancelled", - href: "/bookings/cancelled", - }, -]; -const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const; +const BookingsListingPage = new Proxy<{ + (): JSX.Element; + PageWrapper?: typeof PageWrapper; + getLayout?: typeof getLayout; +}>(BookingsListingView, {}); -const descriptionByStatus: Record, string> = { - upcoming: "upcoming_bookings", - recurring: "recurring_bookings", - past: "past_bookings", - cancelled: "cancelled_bookings", - unconfirmed: "unconfirmed_bookings", -}; - -const querySchema = z.object({ - status: z.enum(validStatuses), -}); - -export default function Bookings() { - const params = useParamsWithFallback(); - const { data: filterQuery } = useFilterQuery(); - const { status } = params ? querySchema.parse(params) : { status: "upcoming" as const }; - const { t } = useLocale(); - const user = useMeQuery().data; - const [isFiltersVisible, setIsFiltersVisible] = useState(false); - - const query = trpc.viewer.bookings.get.useInfiniteQuery( - { - limit: 10, - filters: { - ...filterQuery, - status: filterQuery.status ?? status, - }, - }, - { - // first render has status `undefined` - enabled: true, - getNextPageParam: (lastPage) => lastPage.nextCursor, - } - ); - - // Animate page (tab) transitions to look smoothing - - const buttonInView = useInViewObserver(() => { - if (!query.isFetching && query.hasNextPage && query.status === "success") { - query.fetchNextPage(); - } - }); - - const isEmpty = !query.data?.pages[0]?.bookings.length; - - const shownBookings: Record = {}; - const filterBookings = (booking: BookingOutput) => { - if (status === "recurring" || status == "unconfirmed" || status === "cancelled") { - if (!booking.recurringEventId) { - return true; - } - if ( - shownBookings[booking.recurringEventId] !== undefined && - shownBookings[booking.recurringEventId].length > 0 - ) { - shownBookings[booking.recurringEventId].push(booking); - return false; - } - shownBookings[booking.recurringEventId] = [booking]; - } else if (status === "upcoming") { - return ( - dayjs(booking.startTime).tz(user?.timeZone).format("YYYY-MM-DD") !== - dayjs().tz(user?.timeZone).format("YYYY-MM-DD") - ); - } - return true; - }; - - let recurringInfoToday: RecurringInfo | undefined; - - const bookingsToday = - query.data?.pages.map((page) => - page.bookings.filter((booking: BookingOutput) => { - recurringInfoToday = page.recurringInfo.find( - (info) => info.recurringEventId === booking.recurringEventId - ); - - return ( - dayjs(booking.startTime).tz(user?.timeZone).format("YYYY-MM-DD") === - dayjs().tz(user?.timeZone).format("YYYY-MM-DD") - ); - }) - )[0] || []; - - const [animationParentRef] = useAutoAnimate(); - - return ( - -
-
- - -
- -
-
- {query.status === "error" && ( - - )} - {(query.status === "pending" || query.isPaused) && } - {query.status === "success" && !isEmpty && ( - <> - {!!bookingsToday.length && status === "upcoming" && ( -
- -

{t("today")}

-
- - - - {bookingsToday.map((booking: BookingOutput) => ( - - ))} - - -
-
-
- )} -
-
- - - {query.data.pages.map((page, index) => ( - - {page.bookings.filter(filterBookings).map((booking: BookingOutput) => { - const recurringInfo = page.recurringInfo.find( - (info) => info.recurringEventId === booking.recurringEventId - ); - return ( - - ); - })} - - ))} - -
-
-
- -
-
- - )} - {query.status === "success" && isEmpty && ( -
- -
- )} -
-
-
-
- ); -} - -Bookings.PageWrapper = PageWrapper; -Bookings.getLayout = getLayout; +BookingsListingPage.PageWrapper = PageWrapper; +BookingsListingPage.getLayout = getLayout; export const getStaticPaths: GetStaticPaths = () => { return { @@ -269,4 +28,4 @@ export const getStaticPaths: GetStaticPaths = () => { }; }; -export { getStaticProps }; +export default BookingsListingPage;