diff --git a/apps/web/components/apps/AppList.tsx b/apps/web/components/apps/AppList.tsx index dbd3c79a3a..2e7383599f 100644 --- a/apps/web/components/apps/AppList.tsx +++ b/apps/web/components/apps/AppList.tsx @@ -5,7 +5,7 @@ import { InstallAppButton } from "@calcom/app-store/components"; import { getEventLocationTypeFromApp, type EventLocationType } from "@calcom/app-store/locations"; import type { CredentialOwner } from "@calcom/app-store/types"; import { AppSetDefaultLinkDialog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog"; -import { BulkEditDefaultConferencingModal } from "@calcom/features/eventtypes/components/BulkEditDefaultConferencingModal"; +import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { AppCategories } from "@calcom/prisma/enums"; import { trpc, type RouterOutputs } from "@calcom/trpc"; @@ -154,6 +154,12 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL }); const { t } = useLocale(); + const updateLocationsMutation = trpc.viewer.eventTypes.bulkUpdateToDefaultLocation.useMutation({ + onSuccess: () => { + utils.viewer.getUsersDefaultConferencingApp.invalidate(); + setBulkUpdateModal(false); + }, + }); return ( <> @@ -173,7 +179,12 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL )} {bulkUpdateModal && ( - + )} ); diff --git a/apps/web/pages/availability/index.tsx b/apps/web/pages/availability/index.tsx index 72a952cc43..4fda487a04 100644 --- a/apps/web/pages/availability/index.tsx +++ b/apps/web/pages/availability/index.tsx @@ -1,8 +1,9 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import Link from "next/link"; import { useRouter, usePathname } from "next/navigation"; -import { useCallback } from "react"; +import { useCallback, useState } from "react"; +import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal"; import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules"; import Shell from "@calcom/features/shell/Shell"; import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/components/AvailabilitySliderTable"; @@ -21,6 +22,7 @@ import SkeletonLoader from "@components/availability/SkeletonLoader"; export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availability"]["list"]) { const { t } = useLocale(); + const [bulkUpdateModal, setBulkUpdateModal] = useState(false); const utils = trpc.useContext(); const meQuery = trpc.viewer.me.useQuery(); @@ -65,6 +67,7 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab }), "success" ); + setBulkUpdateModal(true); }, onError: (err) => { if (err instanceof HttpError) { @@ -74,6 +77,15 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab }, }); + const bulkUpdateDefaultAvailabilityMutation = + trpc.viewer.availability.schedule.bulkUpdateToDefaultAvailability.useMutation({ + onSuccess: () => { + utils.viewer.availability.list.invalidate(); + setBulkUpdateModal(false); + showToast(t("success"), "success"); + }, + }); + const duplicateMutation = trpc.viewer.availability.schedule.duplicate.useMutation({ onSuccess: async ({ schedule }) => { await router.push(`/availability/${schedule.id}`); @@ -129,6 +141,14 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab {t("add_a_redirect")} + {bulkUpdateModal && ( + + )} )} diff --git a/packages/features/eventtypes/components/BulkEditDefaultConferencingModal.tsx b/packages/features/eventtypes/components/BulkEditDefaultForEventsModal.tsx similarity index 85% rename from packages/features/eventtypes/components/BulkEditDefaultConferencingModal.tsx rename to packages/features/eventtypes/components/BulkEditDefaultForEventsModal.tsx index d55b5f34b7..07095af885 100644 --- a/packages/features/eventtypes/components/BulkEditDefaultConferencingModal.tsx +++ b/packages/features/eventtypes/components/BulkEditDefaultForEventsModal.tsx @@ -10,7 +10,12 @@ export const BulkUpdateEventSchema = z.object({ eventTypeIds: z.array(z.number()), }); -export function BulkEditDefaultConferencingModal(props: { open: boolean; setOpen: (open: boolean) => void }) { +export function BulkEditDefaultForEventsModal(props: { + open: boolean; + setOpen: (open: boolean) => void; + bulkUpdateFunction: ({ eventTypeIds }: { eventTypeIds: number[] }) => void; + isPending: boolean; +}) { const { t } = useLocale(); const utils = trpc.useContext(); const { data, isFetching } = trpc.viewer.eventTypes.bulkEventFetch.useQuery(); @@ -21,13 +26,6 @@ export function BulkEditDefaultConferencingModal(props: { open: boolean; setOpen }, }); - const updateLocationsMutation = trpc.viewer.eventTypes.bulkUpdateToDefaultLocation.useMutation({ - onSuccess: () => { - utils.viewer.getUsersDefaultConferencingApp.invalidate(); - props.setOpen(false); - }, - }); - const eventTypesSelected = form.watch("eventTypeIds"); if (isFetching || !open || !data?.eventTypes) return null; @@ -42,7 +40,7 @@ export function BulkEditDefaultConferencingModal(props: { open: boolean; setOpen
{ - updateLocationsMutation.mutate(values); + props.bulkUpdateFunction(values); }}>
{data.eventTypes.length > 0 && ( @@ -81,7 +79,7 @@ export function BulkEditDefaultConferencingModal(props: { open: boolean; setOpen utils.viewer.getUsersDefaultConferencingApp.invalidate(); }} /> - diff --git a/packages/trpc/server/routers/viewer/availability/schedule/_router.tsx b/packages/trpc/server/routers/viewer/availability/schedule/_router.tsx index f6e6fde73b..727e764914 100644 --- a/packages/trpc/server/routers/viewer/availability/schedule/_router.tsx +++ b/packages/trpc/server/routers/viewer/availability/schedule/_router.tsx @@ -1,5 +1,6 @@ import authedProcedure from "../../../../procedures/authedProcedure"; import { router } from "../../../../trpc"; +import { ZBulkUpdateToDefaultAvailabilityInputSchema } from "./bulkUpdateDefaultAvailability.schema"; import { ZCreateInputSchema } from "./create.schema"; import { ZDeleteInputSchema } from "./delete.schema"; import { ZScheduleDuplicateSchema } from "./duplicate.schema"; @@ -16,6 +17,7 @@ type ScheduleRouterHandlerCache = { duplicate?: typeof import("./duplicate.handler").duplicateHandler; getScheduleByUserId?: typeof import("./getScheduleByUserId.handler").getScheduleByUserIdHandler; getScheduleByEventSlug?: typeof import("./getScheduleByEventTypeSlug.handler").getScheduleByEventSlugHandler; + bulkUpdateToDefaultAvailability?: typeof import("./bulkUpdateDefaultAvailability.handler").bulkUpdateToDefaultAvailabilityHandler; }; const UNSTABLE_HANDLER_CACHE: ScheduleRouterHandlerCache = {}; @@ -137,4 +139,22 @@ export const scheduleRouter = router({ input, }); }), + bulkUpdateToDefaultAvailability: authedProcedure + .input(ZBulkUpdateToDefaultAvailabilityInputSchema) + .mutation(async ({ ctx, input }) => { + if (!UNSTABLE_HANDLER_CACHE.bulkUpdateToDefaultAvailability) { + UNSTABLE_HANDLER_CACHE.bulkUpdateToDefaultAvailability = await import( + "./bulkUpdateDefaultAvailability.handler" + ).then((mod) => mod.bulkUpdateToDefaultAvailabilityHandler); + } + + if (!UNSTABLE_HANDLER_CACHE.bulkUpdateToDefaultAvailability) { + throw new Error("Failed to load handler"); + } + + return UNSTABLE_HANDLER_CACHE.bulkUpdateToDefaultAvailability({ + ctx, + input, + }); + }), }); diff --git a/packages/trpc/server/routers/viewer/availability/schedule/bulkUpdateDefaultAvailability.handler.ts b/packages/trpc/server/routers/viewer/availability/schedule/bulkUpdateDefaultAvailability.handler.ts new file mode 100644 index 0000000000..6ee101e28e --- /dev/null +++ b/packages/trpc/server/routers/viewer/availability/schedule/bulkUpdateDefaultAvailability.handler.ts @@ -0,0 +1,40 @@ +import { prisma } from "@calcom/prisma"; + +import { TRPCError } from "@trpc/server"; + +import type { TrpcSessionUser } from "../../../../trpc"; +import type { TBulkUpdateToDefaultAvailabilityInputSchema } from "./bulkUpdateDefaultAvailability.schema"; + +type BulkUpdateToDefaultAvailabilityOptions = { + ctx: { + user: NonNullable; + }; + input: TBulkUpdateToDefaultAvailabilityInputSchema; +}; + +export const bulkUpdateToDefaultAvailabilityHandler = async ({ + ctx, + input, +}: BulkUpdateToDefaultAvailabilityOptions) => { + const { eventTypeIds } = input; + const defaultScheduleId = ctx.user.defaultScheduleId; + + if (!defaultScheduleId) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Default schedule not set", + }); + } + + return await prisma.eventType.updateMany({ + where: { + id: { + in: eventTypeIds, + }, + userId: ctx.user.id, + }, + data: { + scheduleId: defaultScheduleId, + }, + }); +}; diff --git a/packages/trpc/server/routers/viewer/availability/schedule/bulkUpdateDefaultAvailability.schema.ts b/packages/trpc/server/routers/viewer/availability/schedule/bulkUpdateDefaultAvailability.schema.ts new file mode 100644 index 0000000000..52374c1d65 --- /dev/null +++ b/packages/trpc/server/routers/viewer/availability/schedule/bulkUpdateDefaultAvailability.schema.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +export const ZBulkUpdateToDefaultAvailabilityInputSchema = z.object({ + eventTypeIds: z.array(z.number()), +}); + +export type TBulkUpdateToDefaultAvailabilityInputSchema = z.infer< + typeof ZBulkUpdateToDefaultAvailabilityInputSchema +>;