From 0757b00db754814a8c1017bc9cf83064bb6bbe45 Mon Sep 17 00:00:00 2001 From: Rajiv Sahal Date: Wed, 15 Oct 2025 17:57:22 +0200 Subject: [PATCH] feat: list schedules atom (#24205) * feat: add api v2 endpoint to fetch all user schedules * chore: update typing * feat: custom hook to fetch all user schedules * refactor: shift logic to transform schedules into function of its own * init: list schedules atom * feat: api endpoints for list schedules atom * refactor: accept redirect url as prop * fix: pass redirect url prop * integrate list schedules atom with availability settings * refactor: extract types to be reused in api v2 endpoints * skip availability settings page for the time being until we have api v2 endpoints in prod * feat: add docs for list schedules atom * fixup * export Schedule type * make sure we always have a default schedule if user deletes his default schedule * chore: implement code rabbit feedback * chore: implement PR feedback * fix: resolve merge conflicts * fix: import path * update platform libraries * fix: type check * resolve merge conflicts * update atoms export * update platform libraries schedule * chore: arrange atoms in alphabetical order * update atoms controller to include endpoints for list schedules atom * add create atom scheule atom * fix: invalidate schedules on new schedule creation * chore: add changesets --- .changeset/lemon-rice-smile.md | 5 + .../controllers/atoms.schedules.controller.ts | 36 +++++ .../(main-nav)/availability/page.tsx | 11 +- .../availability/availability-view.tsx | 1 + docs/mint.json | 15 +- docs/platform/atoms/list-schedules.mdx | 116 ++++++++++++++++ .../schedules/components/ScheduleListItem.tsx | 7 +- .../transformers/getScheduleListItemData.ts | 26 ++++ .../hooks/schedules/useAtomCreateSchedule.ts | 2 + .../schedules/useAtomDuplicateSchedule.ts | 45 ++++++ .../hooks/schedules/useAtomGetAllSchedules.ts | 30 ++++ .../schedules/useEnsureDefaultSchedule.ts | 22 +++ packages/platform/atoms/index.ts | 2 + .../platform/atoms/list-schedules/index.ts | 1 + .../wrappers/ListSchedulesPlatformWrapper.tsx | 129 ++++++++++++++++++ .../[scheduleId].tsx} | 5 + .../base/src/pages/availability/index.tsx | 23 ++++ .../availability-settings-atom.e2e.ts | 13 +- packages/platform/libraries/schedules.ts | 9 ++ .../viewer/availability/list.handler.ts | 5 +- .../schedule/duplicate.handler.ts | 4 +- 21 files changed, 483 insertions(+), 24 deletions(-) create mode 100644 .changeset/lemon-rice-smile.md create mode 100644 docs/platform/atoms/list-schedules.mdx create mode 100644 packages/lib/schedules/transformers/getScheduleListItemData.ts create mode 100644 packages/platform/atoms/hooks/schedules/useAtomDuplicateSchedule.ts create mode 100644 packages/platform/atoms/hooks/schedules/useAtomGetAllSchedules.ts create mode 100644 packages/platform/atoms/hooks/schedules/useEnsureDefaultSchedule.ts create mode 100644 packages/platform/atoms/list-schedules/index.ts create mode 100644 packages/platform/atoms/list-schedules/wrappers/ListSchedulesPlatformWrapper.tsx rename packages/platform/examples/base/src/pages/{availability.tsx => availability/[scheduleId].tsx} (93%) create mode 100644 packages/platform/examples/base/src/pages/availability/index.tsx diff --git a/.changeset/lemon-rice-smile.md b/.changeset/lemon-rice-smile.md new file mode 100644 index 0000000000..e28fb4b96f --- /dev/null +++ b/.changeset/lemon-rice-smile.md @@ -0,0 +1,5 @@ +--- +"@calcom/atoms": minor +--- + +This PR adds an atom to list a user's schedule. Furthermore, there was also another atom introduced which can be used to create a new schedule for a user. diff --git a/apps/api/v2/src/modules/atoms/controllers/atoms.schedules.controller.ts b/apps/api/v2/src/modules/atoms/controllers/atoms.schedules.controller.ts index 7527b44e2f..227a2decd6 100644 --- a/apps/api/v2/src/modules/atoms/controllers/atoms.schedules.controller.ts +++ b/apps/api/v2/src/modules/atoms/controllers/atoms.schedules.controller.ts @@ -25,6 +25,11 @@ import { import { z } from "zod"; import { SCHEDULE_READ, SCHEDULE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants"; +import type { + GetAvailabilityListHandlerReturn, + DuplicateScheduleHandlerReturn, +} from "@calcom/platform-libraries/schedules"; +import { getAvailabilityListHandler, duplicateScheduleHandler } from "@calcom/platform-libraries/schedules"; import type { CreateScheduleHandlerReturn, CreateScheduleSchema } from "@calcom/platform-libraries/schedules"; import { createScheduleHandler } from "@calcom/platform-libraries/schedules"; import { FindDetailedScheduleByIdReturnType } from "@calcom/platform-libraries/schedules"; @@ -68,6 +73,21 @@ export class AtomsSchedulesController { }; } + @Get("/schedules/all") + @Version(VERSION_NEUTRAL) + @UseGuards(ApiAuthGuard) + @Permissions([SCHEDULE_READ]) + async getAllUserSchedules( + @GetUser() user: UserWithProfile + ): Promise>> { + const userSchedules = await getAvailabilityListHandler({ ctx: { user } }); + + return { + status: SUCCESS_STATUS, + data: userSchedules, + }; + } + @Patch("schedules/:scheduleId") @Permissions([SCHEDULE_WRITE]) @UseGuards(ApiAuthGuard) @@ -104,4 +124,20 @@ export class AtomsSchedulesController { data: createdSchedule, }; } + + @Post("schedules/:scheduleId/duplicate") + @Permissions([SCHEDULE_WRITE]) + @UseGuards(ApiAuthGuard) + @ApiOperation({ summary: "Duplicate existing schedule" }) + async duplicateExistingSchedule( + @GetUser() user: UserWithProfile, + @Param("scheduleId", ParseIntPipe) scheduleId: number + ): Promise>> { + const duplicatedSchedule = await duplicateScheduleHandler({ ctx: { user }, input: { scheduleId } }); + + return { + status: SUCCESS_STATUS, + data: duplicatedSchedule, + }; + } } diff --git a/apps/web/app/(use-page-wrapper)/(main-nav)/availability/page.tsx b/apps/web/app/(use-page-wrapper)/(main-nav)/availability/page.tsx index 5ca5e9f438..e9ae0e7de0 100644 --- a/apps/web/app/(use-page-wrapper)/(main-nav)/availability/page.tsx +++ b/apps/web/app/(use-page-wrapper)/(main-nav)/availability/page.tsx @@ -9,6 +9,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository"; import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/components/AvailabilitySliderTable"; +import { getScheduleListItemData } from "@calcom/lib/schedules/transformers/getScheduleListItemData"; import { MembershipRole } from "@calcom/prisma/enums"; import { availabilityRouter } from "@calcom/trpc/server/routers/viewer/availability/_router"; @@ -56,15 +57,7 @@ const Page = async ({ searchParams: _searchParams }: PageProps) => { // This is because the data is cached and as a result the data is converted to a string const availabilities = { ...cachedAvailabilities, - schedules: cachedAvailabilities.schedules.map((schedule) => ({ - ...schedule, - availability: schedule.availability.map((avail) => ({ - ...avail, - startTime: new Date(avail.startTime), - endTime: new Date(avail.endTime), - date: avail.date ? new Date(avail.date) : null, - })), - })), + schedules: cachedAvailabilities.schedules.map((schedule) => getScheduleListItemData(schedule)), }; const organizationId = session?.user?.profile?.organizationId ?? session?.user.org?.id; diff --git a/apps/web/modules/availability/availability-view.tsx b/apps/web/modules/availability/availability-view.tsx index 6b048655f1..9bdb907538 100644 --- a/apps/web/modules/availability/availability-view.tsx +++ b/apps/web/modules/availability/availability-view.tsx @@ -141,6 +141,7 @@ export function AvailabilityList({ availabilities }: AvailabilityListProps) {
    {availabilities.schedules.map((schedule) => ( + `/availability/${scheduleId}`} /> + + ); +} +``` + +For a demonstration of the List schedules atom, please refer to the video below. + +

    + + + +

    + +Below is a list of props that can be passed to the List schedules atom. + +

    + +| Name | Required | Description | +| :------------- | :------- | :---------------------------------------------------------------------------------------------------------------- | +| getRedirectUrl | No | Callback function that determines where the user should be redirected when they click on a schedule from the atom | + +## Combining list schedules atom with availability settings atom + +The List schedules atom works best when combined with the Availability settings atom to provide a seamless experience. In order to combine both of atom, here are the steps we recommend: + + + + Create a page for demostrating the list schedules atom. + + Below code snippet can be used to setup the list schedules atom on a new page + +```js +import { ListSchedules } from "@calcom/atoms"; + +export default function ListSchedules() { + return ( + <> + `/availability/${scheduleId}`} /> + + ); +} +``` + +`getRedirectUrl` prop is a callback function that determines where the user should be redirected when they click on a schedule from the atom. Say if you want to display your availability settings atom on the page `/availability/:scheduleId`, you need to make sure you return the exact same url when you call the `getRedirectUrl` function. + + + + Create a page for demonstrating the availability settings atom. This should be a dynamic route that accepts a schedule id as a query parameter, and then the schedule id gets passed to the availability settings atom. + + Below code snippet can be used to obtain the `scheduleId` and display the availability settings atom + +```js +import { AvailabilitySettings } from "@calcom/atoms"; +import { useRouter } from "next/router"; + +export default function Availability() { + const router = useRouter(); + + return ( + <> + { + console.log("Updated schedule successfully"); + }} + /> + + ) +} +``` + + + + + +-Below video shows a demonstration of how we combined the list schedules atom and availability settings atom in one of our example app to create a seamless availability management experience. + +

    + + + +

    + + + Link to the example app can be found + [here](https://github.com/calcom/cal.com/tree/main/packages/platform/examples/base) + diff --git a/packages/features/schedules/components/ScheduleListItem.tsx b/packages/features/schedules/components/ScheduleListItem.tsx index 1f869e0d8d..d8e6cafd0d 100644 --- a/packages/features/schedules/components/ScheduleListItem.tsx +++ b/packages/features/schedules/components/ScheduleListItem.tsx @@ -26,6 +26,7 @@ export function ScheduleListItem({ updateDefault, isDeletable, duplicateFunction, + redirectUrl, }: { schedule: RouterOutputs["viewer"]["availability"]["list"]["schedules"][number]; deleteFunction: ({ scheduleId }: { scheduleId: number }) => void; @@ -37,6 +38,7 @@ export function ScheduleListItem({ isDeletable: boolean; updateDefault: ({ scheduleId, isDefault }: { scheduleId: number; isDefault: boolean }) => void; duplicateFunction: ({ scheduleId }: { scheduleId: number }) => void; + redirectUrl: string; }) { const { t, i18n } = useLocale(); @@ -44,10 +46,7 @@ export function ScheduleListItem({
  • - +
    {schedule.name} {schedule.isDefault && ( diff --git a/packages/lib/schedules/transformers/getScheduleListItemData.ts b/packages/lib/schedules/transformers/getScheduleListItemData.ts new file mode 100644 index 0000000000..c08e51c2bf --- /dev/null +++ b/packages/lib/schedules/transformers/getScheduleListItemData.ts @@ -0,0 +1,26 @@ +export type Schedule = { + isDefault: boolean; + id: number; + name: string; + timeZone: string | null; + availability: { + id: number; + userId: number | null; + startTime: Date; + endTime: Date; + eventTypeId: number | null; + date: Date | null; + days: number[]; + scheduleId: number | null; + }[]; +}; + +export const getScheduleListItemData = (schedule: Schedule) => ({ + ...schedule, + availability: schedule.availability.map((avail) => ({ + ...avail, + startTime: new Date(avail.startTime), + endTime: new Date(avail.endTime), + date: avail.date ? new Date(avail.date) : null, + })), +}); diff --git a/packages/platform/atoms/hooks/schedules/useAtomCreateSchedule.ts b/packages/platform/atoms/hooks/schedules/useAtomCreateSchedule.ts index 1ee4f61bd6..ef9d7fac11 100644 --- a/packages/platform/atoms/hooks/schedules/useAtomCreateSchedule.ts +++ b/packages/platform/atoms/hooks/schedules/useAtomCreateSchedule.ts @@ -6,6 +6,7 @@ import type { CreateScheduleHandlerReturn } from "@calcom/trpc/server/routers/vi import { TCreateInputSchema as CreateScheduleSchema } from "@calcom/trpc/server/routers/viewer/availability/schedule/create.schema"; import http from "../../lib/http"; +import { QUERY_KEY as SchedulesQueryKey } from "./useAtomGetAllSchedules"; import { QUERY_KEY as ScheduleQueryKey } from "./useAtomSchedule"; interface useAtomCreateScheduleOptions { @@ -35,6 +36,7 @@ export const useAtomCreateSchedule = ( if (data.status === SUCCESS_STATUS) { onSuccess?.(data); queryClient.invalidateQueries({ queryKey: [ScheduleQueryKey] }); + queryClient.invalidateQueries({ queryKey: [SchedulesQueryKey] }); } else { onError?.(data); } diff --git a/packages/platform/atoms/hooks/schedules/useAtomDuplicateSchedule.ts b/packages/platform/atoms/hooks/schedules/useAtomDuplicateSchedule.ts new file mode 100644 index 0000000000..2d74584858 --- /dev/null +++ b/packages/platform/atoms/hooks/schedules/useAtomDuplicateSchedule.ts @@ -0,0 +1,45 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import type { ApiResponse, ApiErrorResponse } from "@calcom/platform-types"; +import type { DuplicateScheduleHandlerReturn } from "@calcom/trpc/server/routers/viewer/availability/schedule/duplicate.handler"; + +import http from "../../lib/http"; +import { QUERY_KEY as ScheduleQueryKey } from "./useAtomSchedule"; + +interface useAtomDuplicateScheduleOptions { + onSuccess?: (res: ApiResponse) => void; + onError?: (err: ApiErrorResponse) => void; +} + +export const useAtomDuplicateSchedule = ( + { onSuccess, onError }: useAtomDuplicateScheduleOptions = { + onSuccess: () => { + return; + }, + onError: () => { + return; + }, + } +) => { + const queryClient = useQueryClient(); + + return useMutation, unknown, { scheduleId: number }>({ + mutationFn: async ({ scheduleId }: { scheduleId: number }) => { + const url = `atoms/schedules/${scheduleId}/duplicate`; + const response = await http.post>(url); + return response.data; + }, + onSuccess: (data) => { + if (data.status === SUCCESS_STATUS) { + onSuccess?.(data); + queryClient.invalidateQueries({ queryKey: [ScheduleQueryKey] }); + } else { + onError?.(data); + } + }, + onError: (err) => { + onError?.(err as ApiErrorResponse); + }, + }); +}; diff --git a/packages/platform/atoms/hooks/schedules/useAtomGetAllSchedules.ts b/packages/platform/atoms/hooks/schedules/useAtomGetAllSchedules.ts new file mode 100644 index 0000000000..f0b1a1327a --- /dev/null +++ b/packages/platform/atoms/hooks/schedules/useAtomGetAllSchedules.ts @@ -0,0 +1,30 @@ +import { useQuery } from "@tanstack/react-query"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import type { ApiResponse } from "@calcom/platform-types"; +import type { GetAvailabilityListHandlerReturn } from "@calcom/trpc/server/routers/viewer/availability/list.handler"; + +import http from "../../lib/http"; +import { useAtomsContext } from "../useAtomsContext"; + +export const QUERY_KEY = "use-atom-user-schedules"; + +export const useAtomGetAllSchedules = () => { + const pathname = "atoms/schedules/all"; + const { isInit, accessToken } = useAtomsContext(); + + const { isLoading, error, data, refetch } = useQuery({ + queryKey: [QUERY_KEY], + queryFn: () => { + return http.get>(pathname).then((res) => { + if (res.data.status === SUCCESS_STATUS) { + return res.data.data; + } + throw new Error(res.data.error?.message); + }); + }, + enabled: isInit && !!accessToken, + }); + + return { isLoading, error, data, refetch }; +}; diff --git a/packages/platform/atoms/hooks/schedules/useEnsureDefaultSchedule.ts b/packages/platform/atoms/hooks/schedules/useEnsureDefaultSchedule.ts new file mode 100644 index 0000000000..ae323785ec --- /dev/null +++ b/packages/platform/atoms/hooks/schedules/useEnsureDefaultSchedule.ts @@ -0,0 +1,22 @@ +import { useEffect, useRef } from "react"; + +import { Schedule } from "@calcom/lib/schedules/transformers/getTransformedSchedles"; + +export function useEnsureDefaultSchedule( + schedules: Schedule[], + onUpdateSchedule: (scheduleId: number) => void | Promise +) { + const onUpdateRef = useRef(onUpdateSchedule); + + useEffect(() => { + onUpdateRef.current = onUpdateSchedule; + }); + + useEffect(() => { + const defaultSchedule = schedules.filter((schedule) => schedule.isDefault); + + if (defaultSchedule.length === 0 && schedules.length > 0) { + onUpdateRef.current(schedules[0].id); + } + }, [schedules]); +} diff --git a/packages/platform/atoms/index.ts b/packages/platform/atoms/index.ts index c0062ee9b6..8131944872 100644 --- a/packages/platform/atoms/index.ts +++ b/packages/platform/atoms/index.ts @@ -46,3 +46,5 @@ export { CalendarViewPlatformWrapper as CalendarView } from "./calendar-view/ind export { CreateSchedulePlatformWrapper as CreateSchedule } from "./create-schedule/index"; export { CreateScheduleForm } from "./create-schedule/CreateScheduleForm"; + +export { ListSchedulesPlatformWrapper as ListSchedules } from "./list-schedules/index"; diff --git a/packages/platform/atoms/list-schedules/index.ts b/packages/platform/atoms/list-schedules/index.ts new file mode 100644 index 0000000000..67c749392d --- /dev/null +++ b/packages/platform/atoms/list-schedules/index.ts @@ -0,0 +1 @@ +export { ListSchedulesPlatformWrapper } from "./wrappers/ListSchedulesPlatformWrapper"; diff --git a/packages/platform/atoms/list-schedules/wrappers/ListSchedulesPlatformWrapper.tsx b/packages/platform/atoms/list-schedules/wrappers/ListSchedulesPlatformWrapper.tsx new file mode 100644 index 0000000000..ea31671adb --- /dev/null +++ b/packages/platform/atoms/list-schedules/wrappers/ListSchedulesPlatformWrapper.tsx @@ -0,0 +1,129 @@ +import { AtomsWrapper } from "@/components/atoms-wrapper"; +import { useAutoAnimate } from "@formkit/auto-animate/react"; + +import { ScheduleListItem } from "@calcom/features/schedules/components/ScheduleListItem"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { getScheduleListItemData } from "@calcom/lib/schedules/transformers/getScheduleListItemData"; +import { EmptyScreen } from "@calcom/ui/components/empty-screen"; + +import { useAtomDuplicateSchedule } from "../../hooks/schedules/useAtomDuplicateSchedule"; +import { useAtomGetAllSchedules } from "../../hooks/schedules/useAtomGetAllSchedules"; +import { useAtomUpdateSchedule } from "../../hooks/schedules/useAtomUpdateSchedule"; +import useDeleteSchedule from "../../hooks/schedules/useDeleteSchedule"; +import { useEnsureDefaultSchedule } from "../../hooks/schedules/useEnsureDefaultSchedule"; +import { useMe } from "../../hooks/useMe"; +import { useToast } from "../../src/components/ui/use-toast"; + +interface ListSchedulesPlatformWrapperProps { + getScheduleUrl?: (scheduleId: number) => string; +} + +export const ListSchedulesPlatformWrapper = ({ getScheduleUrl }: ListSchedulesPlatformWrapperProps = {}) => { + const [animationParentRef] = useAutoAnimate(); + const { toast } = useToast(); + const { t } = useLocale(); + + const { + data: userSchedules, + isLoading: isLoadingSchedules, + refetch: refetchSchedules, + } = useAtomGetAllSchedules(); + const { data: user, isLoading: isUserLoading } = useMe(); + + const { mutate: updateSchedule } = useAtomUpdateSchedule({ + onSuccess: () => { + toast({ + description: "Schedule updated successfully", + }); + refetchSchedules(); + }, + onError: (err) => { + toast({ + description: `Could not update schedule: ${err.error.message}`, + }); + }, + }); + + const { mutate: duplicateSchedule } = useAtomDuplicateSchedule({ + onSuccess: () => { + toast({ + description: "Schedule created successfully", + }); + refetchSchedules(); + }, + onError: (err) => { + toast({ + description: `Could not duplicate schedule: ${err.error.message}`, + }); + }, + }); + + const { mutate: deleteSchedule } = useDeleteSchedule({ + onSuccess: () => { + toast({ + description: "Schedule deleted successfully", + }); + refetchSchedules(); + }, + onError: (err) => { + toast({ + description: `Could not delete schedule: ${err.error.message}`, + }); + }, + }); + + useEnsureDefaultSchedule(userSchedules?.schedules ?? [], (id) => { + updateSchedule({ + scheduleId: id, + body: { + isDefault: true, + }, + }); + }); + + const transformedSchedules = userSchedules?.schedules.map((schedule) => getScheduleListItemData(schedule)); + + if (isLoadingSchedules || isUserLoading) return <>{t("loading")}; + + if (!isLoadingSchedules && transformedSchedules?.length === 0) + return ( + + ); + + return ( + +
    +
      + {transformedSchedules?.map((schedule) => ( + { + updateSchedule({ + scheduleId: schedule.id, + body: { + isDefault: true, + }, + }); + }} + deleteFunction={async () => await deleteSchedule({ id: schedule.id })} + duplicateFunction={() => duplicateSchedule({ scheduleId: schedule.id })} + /> + ))} +
    +
    +
    + ); +}; diff --git a/packages/platform/examples/base/src/pages/availability.tsx b/packages/platform/examples/base/src/pages/availability/[scheduleId].tsx similarity index 93% rename from packages/platform/examples/base/src/pages/availability.tsx rename to packages/platform/examples/base/src/pages/availability/[scheduleId].tsx index 876e2f916b..090b8b5742 100644 --- a/packages/platform/examples/base/src/pages/availability.tsx +++ b/packages/platform/examples/base/src/pages/availability/[scheduleId].tsx @@ -1,5 +1,7 @@ import { Navbar } from "@/components/Navbar"; import { Inter } from "next/font/google"; +// eslint-disable-next-line @calcom/eslint/deprecated-imports-next-router +import { useRouter } from "next/router"; import { useRef, useCallback } from "react"; import type { AvailabilitySettingsFormRef } from "@calcom/atoms"; @@ -8,6 +10,7 @@ import { AvailabilitySettings } from "@calcom/atoms"; const inter = Inter({ subsets: ["latin"] }); export default function Availability(props: { calUsername: string; calEmail: string }) { + const router = useRouter(); const availabilityRef = useRef(null); const handleFormStateChange = useCallback((formState: unknown) => { @@ -50,6 +53,7 @@ export default function Availability(props: { calUsername: string; calEmail: str
    { console.log("Updated successfully"); + router.push(`/availability`); }} onUpdateError={() => { console.log("update error"); diff --git a/packages/platform/examples/base/src/pages/availability/index.tsx b/packages/platform/examples/base/src/pages/availability/index.tsx new file mode 100644 index 0000000000..fc370f28ce --- /dev/null +++ b/packages/platform/examples/base/src/pages/availability/index.tsx @@ -0,0 +1,23 @@ +import { Navbar } from "@/components/Navbar"; +import { Inter } from "next/font/google"; + +import { ListSchedules, CreateSchedule } from "@calcom/atoms"; + +const inter = Inter({ subsets: ["latin"] }); + +export default function Availability(props: { calUsername: string; calEmail: string }) { + return ( +
    + +
    +
    + +
    + +
    + `/availability/${scheduleId}`} /> +
    +
    +
    + ); +} diff --git a/packages/platform/examples/base/tests/availability-settings-atom/availability-settings-atom.e2e.ts b/packages/platform/examples/base/tests/availability-settings-atom/availability-settings-atom.e2e.ts index 70bc6fda8e..2f741fecd2 100644 --- a/packages/platform/examples/base/tests/availability-settings-atom/availability-settings-atom.e2e.ts +++ b/packages/platform/examples/base/tests/availability-settings-atom/availability-settings-atom.e2e.ts @@ -8,11 +8,20 @@ async function selectOption(page: Page, optionNumber: number) { await page.keyboard.press("Enter"); } -test("availability page loads with all components", async ({ page }) => { +// eslint-disable-next-line playwright/no-skipped-test +test.skip("availability page loads with all components", async ({ page }) => { await page.goto("/availability"); - await expect(page).toHaveURL("/availability"); + await expect(page.locator('[data-testid="list-schedules-atom"]')).toBeVisible(); + await expect(page.locator('[data-testid="schedules"]')).toBeVisible(); + + await expect(page.locator('[data-testid="schedules"] li').first()).toBeVisible(); + await expect( + page.locator('[data-testid="schedules"] li').first().locator('[data-testid="schedule-more"]') + ).toBeVisible(); + await page.locator('[data-testid="schedules"] li').first().locator('[data-testid="schedule-more"]').click(); + await expect(page.locator('h1:has-text("Availability Settings")')).toBeVisible(); await expect(page.locator('button:has-text("Validate Form")')).toBeVisible(); diff --git a/packages/platform/libraries/schedules.ts b/packages/platform/libraries/schedules.ts index 578c4ede61..c3c30c73f7 100644 --- a/packages/platform/libraries/schedules.ts +++ b/packages/platform/libraries/schedules.ts @@ -11,3 +11,12 @@ export { type CreateScheduleHandlerReturn, } from "@calcom/trpc/server/routers/viewer/availability/schedule/create.handler"; export { ZCreateInputSchema as CreateScheduleSchema } from "@calcom/trpc/server/routers/viewer/availability/schedule/create.schema"; + +export { + listHandler as getAvailabilityListHandler, + type GetAvailabilityListHandlerReturn, +} from "@calcom/trpc/server/routers/viewer/availability/list.handler"; +export { + duplicateHandler as duplicateScheduleHandler, + type DuplicateScheduleHandlerReturn, +} from "@calcom/trpc/server/routers/viewer/availability/schedule/duplicate.handler"; diff --git a/packages/trpc/server/routers/viewer/availability/list.handler.ts b/packages/trpc/server/routers/viewer/availability/list.handler.ts index ba86f2b360..cae273f534 100644 --- a/packages/trpc/server/routers/viewer/availability/list.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/list.handler.ts @@ -5,10 +5,12 @@ import { getDefaultScheduleId } from "./util"; type ListOptions = { ctx: { - user: NonNullable; + user: Pick, "id" | "defaultScheduleId">; }; }; +export type GetAvailabilityListHandlerReturn = Awaited>; + export const listHandler = async ({ ctx }: ListOptions) => { const { user } = ctx; @@ -47,6 +49,7 @@ export const listHandler = async ({ ctx }: ListOptions) => { }, }); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { defaultScheduleId = null; } diff --git a/packages/trpc/server/routers/viewer/availability/schedule/duplicate.handler.ts b/packages/trpc/server/routers/viewer/availability/schedule/duplicate.handler.ts index 642c952de2..cfaac9e1f5 100644 --- a/packages/trpc/server/routers/viewer/availability/schedule/duplicate.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/schedule/duplicate.handler.ts @@ -8,11 +8,13 @@ import type { TScheduleDuplicateSchema } from "./duplicate.schema"; type DuplicateScheduleOptions = { ctx: { - user: NonNullable; + user: Pick, "id" | "timeZone">; }; input: TScheduleDuplicateSchema; }; +export type DuplicateScheduleHandlerReturn = Awaited>; + export const duplicateHandler = async ({ ctx, input }: DuplicateScheduleOptions) => { try { const { scheduleId } = input;