From a957c15062fafaa73692ec41b39cf588588eea18 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Wed, 23 Apr 2025 11:31:01 -0300 Subject: [PATCH] perf: SSR - /availablilty/[schedule] (#20829) * ssr for schedule page * improve skeleton * improve skeleton * improve skeleton --- .../availability/[schedule]/actions.ts | 7 + .../availability/[schedule]/loading.tsx | 5 + .../availability/[schedule]/page.tsx | 23 ++- .../availability/[schedule]/skeleton.tsx | 132 ++++++++++++++++++ .../availability/[schedule]/schedule-view.tsx | 46 ++---- 5 files changed, 178 insertions(+), 35 deletions(-) create mode 100644 apps/web/app/(use-page-wrapper)/availability/[schedule]/actions.ts create mode 100644 apps/web/app/(use-page-wrapper)/availability/[schedule]/loading.tsx create mode 100644 apps/web/app/(use-page-wrapper)/availability/[schedule]/skeleton.tsx diff --git a/apps/web/app/(use-page-wrapper)/availability/[schedule]/actions.ts b/apps/web/app/(use-page-wrapper)/availability/[schedule]/actions.ts new file mode 100644 index 0000000000..8fa1f7f746 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/availability/[schedule]/actions.ts @@ -0,0 +1,7 @@ +"use server"; + +import { revalidatePath } from "next/cache"; + +export async function revalidateSchedulePage(scheduleId: number) { + revalidatePath(`/availability/${scheduleId}`); +} diff --git a/apps/web/app/(use-page-wrapper)/availability/[schedule]/loading.tsx b/apps/web/app/(use-page-wrapper)/availability/[schedule]/loading.tsx new file mode 100644 index 0000000000..2438a4c78c --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/availability/[schedule]/loading.tsx @@ -0,0 +1,5 @@ +import { AvailabilityScheduleSkeleton } from "./skeleton"; + +export default function Loading() { + return ; +} diff --git a/apps/web/app/(use-page-wrapper)/availability/[schedule]/page.tsx b/apps/web/app/(use-page-wrapper)/availability/[schedule]/page.tsx index eaa36c6f22..1f6a8b1f3f 100644 --- a/apps/web/app/(use-page-wrapper)/availability/[schedule]/page.tsx +++ b/apps/web/app/(use-page-wrapper)/availability/[schedule]/page.tsx @@ -1,8 +1,12 @@ +import { createRouterCaller } from "app/_trpc/context"; import type { PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { notFound } from "next/navigation"; import { z } from "zod"; +import { availabilityRouter } from "@calcom/trpc/server/routers/viewer/availability/_router"; +import { travelSchedulesRouter } from "@calcom/trpc/server/routers/viewer/travelSchedules/_router"; + import { AvailabilitySettingsWebWrapper } from "~/availability/[schedule]/schedule-view"; const querySchema = z.object({ @@ -29,8 +33,25 @@ const Page = async ({ params }: PageProps) => { if (!parsed.success) { notFound(); } + const scheduleId = parsed.data.schedule; - return ; + const [availabilityCaller, travelSchedulesCaller] = await Promise.all([ + createRouterCaller(availabilityRouter), + createRouterCaller(travelSchedulesRouter), + ]); + + const [scheduleData, travelSchedulesData] = await Promise.all([ + availabilityCaller.schedule.get({ scheduleId }), + travelSchedulesCaller.get(), + ]); + + if (!scheduleData) { + notFound(); + } + + return ( + + ); }; export default Page; diff --git a/apps/web/app/(use-page-wrapper)/availability/[schedule]/skeleton.tsx b/apps/web/app/(use-page-wrapper)/availability/[schedule]/skeleton.tsx new file mode 100644 index 0000000000..06ecc6cbc3 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/availability/[schedule]/skeleton.tsx @@ -0,0 +1,132 @@ +"use client"; + +import Shell from "@calcom/features/shell/Shell"; +import { SkeletonText, SkeletonContainer, SkeletonButton } from "@calcom/ui/components/skeleton"; + +export function AvailabilityScheduleSkeleton() { + return ( + + +
+ {/* Header section */} +
+ {/* Back button */} +
+ {/* Back button */} +
+ + {/* Title section */} +
+
+ + {/* Title */} + {/* Subtitle */} +
+ + {/* Action buttons */} +
+ {/* Desktop buttons - Set to Default, Delete, and Save */} +
+ {/* Set to Default - desktop only */} +
+ {/* "Set to Default" text */} +
{/* Toggle */} +
{/* Divider */} +
+ {/* Delete button */} + + {/* Second divider */} +
{/* Divider */} + {/* Save button - updated to match screenshot */} + {/* Save button */} +
+
+
+
+ + {/* Main content */} +
+
+
+ {/* Schedule container */} +
+
+ {/* Days of the week */} + {Array(7) + .fill(0) + .map((_, i) => ( +
+ {/* Day toggle and name */} +
+
+
+
{/* Toggle */} + {/* Day name */} +
+
+
+ + {/* Time inputs - conditional rendering based on day being active */} + {i !== 0 && + i !== 6 && ( // Only show time inputs for Monday-Friday +
+
+
+
+ {" "} + {/* Time input */} + - + {" "} + {/* Time input */} +
+ {/* Add button */} +
+
+ {/* Copy button */} +
+ )} +
+ ))} +
+
+ + {/* Date overrides section */} +
+
+ {/* Date overrides */} + {/* Info icon */} +
+ {/* Description */} +
+ {/* Add an override button */} +
+
+
+ + {/* Sidebar - only visible on desktop */} +
+
+
+ {/* Timezone */} + {/* Timezone selector */} +
+ +
+ +
+ {/* Something doesn't look right? */} +
+ {/* Launch troubleshooter */} +
+
+
+
+
+
+
+ + + ); +} diff --git a/apps/web/modules/availability/[schedule]/schedule-view.tsx b/apps/web/modules/availability/[schedule]/schedule-view.tsx index fb01385536..fcb71b8b40 100644 --- a/apps/web/modules/availability/[schedule]/schedule-view.tsx +++ b/apps/web/modules/availability/[schedule]/schedule-view.tsx @@ -1,51 +1,36 @@ "use client"; -import { useRouter } from "next/navigation"; +import { revalidateSchedulePage } from "app/(use-page-wrapper)/availability/[schedule]/actions"; +import { useRouter, useSearchParams } from "next/navigation"; import { useState } from "react"; import { AvailabilitySettings } from "@calcom/atoms/availability/AvailabilitySettings"; import type { BulkUpdatParams } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal"; import { withErrorFromUnknown } from "@calcom/lib/getClientErrorFromUnknown"; -import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; -import type { ScheduleRepository } from "@calcom/lib/server/repository/schedule"; -import type { TravelScheduleRepository } from "@calcom/lib/server/repository/travelSchedule"; +import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; import { showToast } from "@calcom/ui/components/toast"; type PageProps = { - scheduleFetched?: Awaited>; - travelSchedules?: Awaited>; + scheduleData: RouterOutputs["viewer"]["availability"]["schedule"]["get"]; + travelSchedulesData: RouterOutputs["viewer"]["travelSchedules"]["get"]; }; export const AvailabilitySettingsWebWrapper = ({ - scheduleFetched: scheduleProp, - travelSchedules: travelSchedulesProp, + scheduleData: schedule, + travelSchedulesData: travelSchedules, }: PageProps) => { - const searchParams = useCompatSearchParams(); + const searchParams = useSearchParams(); const { t } = useLocale(); const router = useRouter(); const utils = trpc.useUtils(); const me = useMeQuery(); - const scheduleId = searchParams?.get("schedule") ? Number(searchParams.get("schedule")) : -1; const fromEventType = searchParams?.get("fromEventType"); + const scheduleId = schedule.id; const { timeFormat } = me.data || { timeFormat: null }; - const { data: scheduleData, isPending: isFetchingPending } = trpc.viewer.availability.schedule.get.useQuery( - { scheduleId }, - { - enabled: !!scheduleId && !scheduleProp, - } - ); - const isPending = isFetchingPending && !scheduleProp; - const schedule = scheduleProp ?? scheduleData; - - const { data: travelSchedulesData } = trpc.viewer.travelSchedules.get.useQuery(undefined, { - enabled: !travelSchedulesProp, - }); - const travelSchedules = travelSchedulesProp ?? travelSchedulesData; - const [isBulkUpdateModalOpen, setIsBulkUpdateModalOpen] = useState(false); const bulkUpdateDefaultAvailabilityMutation = trpc.viewer.availability.schedule.bulkUpdateToDefaultAvailability.useMutation(); @@ -57,7 +42,7 @@ export const AvailabilitySettingsWebWrapper = ({ bulkUpdateDefaultAvailabilityMutation.mutate( { eventTypeIds, - selectedDefaultScheduleId: scheduleData?.id, + selectedDefaultScheduleId: scheduleId, }, { onSuccess: () => { @@ -86,6 +71,7 @@ export const AvailabilitySettingsWebWrapper = ({ } } utils.viewer.availability.schedule.get.invalidate({ scheduleId: data.schedule.id }); + revalidateSchedulePage(scheduleId); utils.viewer.availability.list.invalidate(); showToast( t("availability_updated_successfully", { @@ -115,20 +101,12 @@ export const AvailabilitySettingsWebWrapper = ({ }, }); - // TODO: reimplement Skeletons for this page in here - if (isPending) return null; - - // We wait for the schedule to be loaded before rendering the form inside AvailabilitySettings - // since `defaultValues` cannot be redeclared after first render and using `values` will - // trigger a form reset when revalidating. Introducing flaky behavior. - if (!schedule) return null; - return (