diff --git a/apps/web/app/future/availability/[schedule]/page.tsx b/apps/web/app/future/availability/[schedule]/page.tsx
index 96c99517ab..88588d9de3 100644
--- a/apps/web/app/future/availability/[schedule]/page.tsx
+++ b/apps/web/app/future/availability/[schedule]/page.tsx
@@ -1,8 +1,13 @@
import type { PageProps } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
+import { getServerSession } from "next-auth";
+import { notFound } from "next/navigation";
+import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
import { ScheduleRepository } from "@calcom/lib/server/repository/schedule";
+import { TravelScheduleRepository } from "@calcom/lib/server/repository/travelSchedule";
+import { UserRepository } from "@calcom/lib/server/repository/user";
import { AvailabilitySettingsWebWrapper } from "~/availability/[schedule]/schedule-view";
@@ -23,4 +28,50 @@ export const generateMetadata = async ({ params }: PageProps) => {
);
};
-export default WithLayout({ getLayout: null, Page: AvailabilitySettingsWebWrapper });
+const Page = async ({ params }: PageProps) => {
+ if (params?.schedule) {
+ notFound();
+ }
+ const scheduleId = Number(params.schedule);
+
+ const session = await getServerSession(AUTH_OPTIONS);
+ const userId = session?.user?.id;
+ if (!userId) {
+ notFound();
+ }
+
+ let userData, schedule, travelSchedules;
+
+ try {
+ userData = await UserRepository.findById({
+ id: userId,
+ select: {
+ timeZone: true,
+ defaultScheduleId: true,
+ },
+ });
+ if (!userData?.timeZone || !userData?.defaultScheduleId) {
+ throw new Error("timeZone and defaultScheduleId not found");
+ }
+ } catch (e) {
+ notFound();
+ }
+
+ try {
+ schedule = await ScheduleRepository.findDetailedScheduleById({
+ scheduleId,
+ isManagedEventType: false,
+ userId,
+ timeZone: userData.timeZone,
+ defaultScheduleId: userData.defaultScheduleId,
+ });
+ } catch (e) {}
+
+ try {
+ travelSchedules = await TravelScheduleRepository.findTravelSchedulesByUserId(userId);
+ } catch (e) {}
+
+ return ;
+};
+
+export default WithLayout({ ServerPage: Page });
diff --git a/apps/web/app/future/availability/page.tsx b/apps/web/app/future/availability/page.tsx
index f793a90fec..c75021f80a 100644
--- a/apps/web/app/future/availability/page.tsx
+++ b/apps/web/app/future/availability/page.tsx
@@ -1,5 +1,10 @@
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
+import { getServerSession } from "next-auth";
+import { notFound } from "next/navigation";
+
+import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
+import { OrganizationRepository } from "@calcom/lib/server/repository/organization";
import AvailabilityPage from "~/availability/availability-view";
@@ -10,4 +15,23 @@ export const generateMetadata = async () => {
);
};
-export default WithLayout({ getLayout: null, Page: AvailabilityPage });
+const Page = async () => {
+ const session = await getServerSession(AUTH_OPTIONS);
+ const userId = session?.user?.id;
+ const orgId = session?.user?.org?.id;
+ if (!userId || !orgId) {
+ notFound();
+ }
+
+ try {
+ const currentOrg = await OrganizationRepository.findCurrentOrg({
+ orgId,
+ userId,
+ });
+ return ;
+ } catch {
+ notFound();
+ }
+};
+
+export default WithLayout({ ServerPage: Page });
diff --git a/apps/web/modules/availability/[schedule]/schedule-view.tsx b/apps/web/modules/availability/[schedule]/schedule-view.tsx
index 03fc0108c3..4876b29f47 100644
--- a/apps/web/modules/availability/[schedule]/schedule-view.tsx
+++ b/apps/web/modules/availability/[schedule]/schedule-view.tsx
@@ -8,11 +8,21 @@ 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 { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { showToast } from "@calcom/ui";
-export const AvailabilitySettingsWebWrapper = () => {
+type PageProps = {
+ schedule?: Awaited>;
+ travelSchedules?: Awaited>;
+};
+
+export const AvailabilitySettingsWebWrapper = ({
+ schedule: scheduleProp,
+ travelSchedules: travelSchedulesProp,
+}: PageProps) => {
const searchParams = useCompatSearchParams();
const { t } = useLocale();
const router = useRouter();
@@ -21,14 +31,19 @@ export const AvailabilitySettingsWebWrapper = () => {
const scheduleId = searchParams?.get("schedule") ? Number(searchParams.get("schedule")) : -1;
const fromEventType = searchParams?.get("fromEventType");
const { timeFormat } = me.data || { timeFormat: null };
- const { data: schedule, isPending } = trpc.viewer.availability.schedule.get.useQuery(
+ const { data: scheduleData, isPending: isFetchingPending } = trpc.viewer.availability.schedule.get.useQuery(
{ scheduleId },
{
- enabled: !!scheduleId,
+ enabled: !!scheduleId && !scheduleProp,
}
);
+ const isPending = isFetchingPending && !scheduleProp;
+ const schedule = scheduleProp ?? scheduleData;
- const { data: travelSchedules } = trpc.viewer.getTravelSchedules.useQuery();
+ const { data: travelSchedulesData } = trpc.viewer.getTravelSchedules.useQuery(undefined, {
+ enabled: !travelSchedulesProp,
+ });
+ const travelSchedules = travelSchedulesProp ?? travelSchedulesData;
const [isBulkUpdateModalOpen, setIsBulkUpdateModalOpen] = useState(false);
const bulkUpdateDefaultAvailabilityMutation =
diff --git a/apps/web/modules/availability/availability-view.tsx b/apps/web/modules/availability/availability-view.tsx
index 378ed5c28a..fad17f183b 100644
--- a/apps/web/modules/availability/availability-view.tsx
+++ b/apps/web/modules/availability/availability-view.tsx
@@ -13,6 +13,7 @@ import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/compone
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
+import type { OrganizationRepository } from "@calcom/lib/server/repository/organization";
import { MembershipRole } from "@calcom/prisma/enums";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
@@ -170,13 +171,18 @@ function AvailabilityListWithQuery() {
);
}
-export default function AvailabilityPage() {
+type PageProps = {
+ currentOrg?: Awaited> | null;
+};
+
+export default function AvailabilityPage({ currentOrg }: PageProps) {
const { t } = useLocale();
const searchParams = useCompatSearchParams();
const router = useRouter();
const pathname = usePathname();
const me = useMeQuery();
- const { data } = trpc.viewer.organizations.listCurrent.useQuery();
+ const { data: _data } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { enabled: !currentOrg });
+ const data = currentOrg ?? _data;
// Get a new searchParams string by merging the current
// searchParams with a provided key/value pair
diff --git a/packages/lib/hasEditPermissionForUser.ts b/packages/lib/hasEditPermissionForUser.ts
index 6ba4e49df0..5da76db06d 100644
--- a/packages/lib/hasEditPermissionForUser.ts
+++ b/packages/lib/hasEditPermissionForUser.ts
@@ -38,19 +38,20 @@ export async function hasEditPermissionForUserID({ ctx, input }: InputOptions) {
return teamIdOverlaps;
}
-export async function hasReadPermissionsForUserId({ ctx, input }: InputOptions) {
- const { user } = ctx;
-
+export async function hasReadPermissionsForUserId({
+ userId,
+ memberId,
+}: InputOptions["input"] & { userId: number }) {
const authedUsersTeams = await prisma.membership.findMany({
where: {
- userId: user.id,
+ userId,
accepted: true,
},
});
const targetUsersTeams = await prisma.membership.findMany({
where: {
- userId: input.memberId,
+ userId: memberId,
accepted: true,
},
});
diff --git a/packages/lib/server/repository/schedule.ts b/packages/lib/server/repository/schedule.ts
index a9e75de317..e44b429583 100644
--- a/packages/lib/server/repository/schedule.ts
+++ b/packages/lib/server/repository/schedule.ts
@@ -1,4 +1,12 @@
import prisma from "@calcom/prisma";
+import { getDefaultScheduleId } from "@calcom/trpc/server/routers/viewer/availability/util";
+
+import { hasReadPermissionsForUserId } from "../../hasEditPermissionForUser";
+import {
+ transformAvailabilityForAtom,
+ transformDateOverridesForAtom,
+ transformWorkingHoursForAtom,
+} from "../../schedules";
export class ScheduleRepository {
static async findScheduleById({ id }: { id: number }) {
@@ -17,4 +25,64 @@ export class ScheduleRepository {
return schedule;
}
+
+ static async findDetailedScheduleById({
+ isManagedEventType,
+ scheduleId,
+ userId,
+ defaultScheduleId,
+ timeZone: userTimeZone,
+ }: {
+ timeZone: string;
+ userId: number;
+ defaultScheduleId: number | null;
+ scheduleId?: number;
+ isManagedEventType?: boolean;
+ }) {
+ const schedule = await prisma.schedule.findUnique({
+ where: {
+ id: scheduleId || (await getDefaultScheduleId(userId, prisma)),
+ },
+ select: {
+ id: true,
+ userId: true,
+ name: true,
+ availability: true,
+ timeZone: true,
+ },
+ });
+
+ if (!schedule) {
+ throw new Error("Schedule not found");
+ }
+ const isCurrentUserPartOfTeam = hasReadPermissionsForUserId({ memberId: schedule?.userId, userId });
+
+ const isCurrentUserOwner = schedule?.userId === userId;
+
+ if (!isCurrentUserPartOfTeam && !isCurrentUserOwner) {
+ throw new Error("UNAUTHORIZED");
+ }
+
+ const timeZone = schedule.timeZone || userTimeZone;
+
+ const schedulesCount = await prisma.schedule.count({
+ where: {
+ userId: userId,
+ },
+ });
+ // disabling utc casting while fetching WorkingHours
+ return {
+ id: schedule.id,
+ name: schedule.name,
+ isManaged: schedule.userId !== userId,
+ workingHours: transformWorkingHoursForAtom(schedule),
+ schedule: schedule.availability,
+ availability: transformAvailabilityForAtom(schedule),
+ timeZone,
+ dateOverrides: transformDateOverridesForAtom(schedule, timeZone),
+ isDefault: !scheduleId || defaultScheduleId === schedule.id,
+ isLastSchedule: schedulesCount <= 1,
+ readOnly: schedule.userId !== userId && !isManagedEventType,
+ };
+ }
}
diff --git a/packages/lib/server/repository/travelSchedule.ts b/packages/lib/server/repository/travelSchedule.ts
new file mode 100644
index 0000000000..933f01162d
--- /dev/null
+++ b/packages/lib/server/repository/travelSchedule.ts
@@ -0,0 +1,19 @@
+import { prisma } from "@calcom/prisma";
+
+export class TravelScheduleRepository {
+ static async findTravelSchedulesByUserId(userId: number) {
+ const allTravelSchedules = await prisma.travelSchedule.findMany({
+ where: {
+ userId,
+ },
+ select: {
+ id: true,
+ startDate: true,
+ endDate: true,
+ timeZone: true,
+ },
+ });
+
+ return allTravelSchedules;
+ }
+}
diff --git a/packages/lib/server/repository/user.ts b/packages/lib/server/repository/user.ts
index 8e39cfb36e..91c7a29f6c 100644
--- a/packages/lib/server/repository/user.ts
+++ b/packages/lib/server/repository/user.ts
@@ -238,12 +238,12 @@ export class UserRepository {
};
}
- static async findById({ id }: { id: number }) {
+ static async findById({ id, select }: { id: number; select?: Prisma.UserSelect }) {
const user = await prisma.user.findUnique({
where: {
id,
},
- select: userSelect,
+ select: { ...userSelect, ...select },
});
if (!user) {
diff --git a/packages/trpc/server/routers/loggedInViewer/getTravelSchedules.handler.ts b/packages/trpc/server/routers/loggedInViewer/getTravelSchedules.handler.ts
index a47e454ae5..7f3b1d4768 100644
--- a/packages/trpc/server/routers/loggedInViewer/getTravelSchedules.handler.ts
+++ b/packages/trpc/server/routers/loggedInViewer/getTravelSchedules.handler.ts
@@ -1,4 +1,4 @@
-import { prisma } from "@calcom/prisma";
+import { TravelScheduleRepository } from "@calcom/lib/server/repository/travelSchedule";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
type GetTravelSchedulesOptions = {
@@ -8,17 +8,5 @@ type GetTravelSchedulesOptions = {
};
export const getTravelSchedulesHandler = async ({ ctx }: GetTravelSchedulesOptions) => {
- const allTravelSchedules = await prisma.travelSchedule.findMany({
- where: {
- userId: ctx.user.id,
- },
- select: {
- id: true,
- startDate: true,
- endDate: true,
- timeZone: true,
- },
- });
-
- return allTravelSchedules;
+ return await TravelScheduleRepository.findTravelSchedulesByUserId(ctx.user.id);
};
diff --git a/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts b/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts
index 0ede8713f0..affcad719a 100644
--- a/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts
+++ b/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts
@@ -1,15 +1,6 @@
-import {
- transformAvailabilityForAtom,
- transformDateOverridesForAtom,
- transformWorkingHoursForAtom,
-} from "@calcom/lib";
-import { hasReadPermissionsForUserId } from "@calcom/lib/hasEditPermissionForUser";
-import { prisma } from "@calcom/prisma";
-
-import { TRPCError } from "@trpc/server";
+import { ScheduleRepository } from "@calcom/lib/server/repository/schedule";
import type { TrpcSessionUser } from "../../../../trpc";
-import { getDefaultScheduleId } from "../util";
import type { TGetInputSchema } from "./get.schema";
type GetOptions = {
@@ -20,58 +11,11 @@ type GetOptions = {
};
export const getHandler = async ({ ctx, input }: GetOptions) => {
- const { user } = ctx;
-
- const schedule = await prisma.schedule.findUnique({
- where: {
- id: input.scheduleId || (await getDefaultScheduleId(user.id, prisma)),
- },
- select: {
- id: true,
- userId: true,
- name: true,
- availability: true,
- timeZone: true,
- },
+ return await ScheduleRepository.findDetailedScheduleById({
+ scheduleId: input.scheduleId,
+ isManagedEventType: input.isManagedEventType,
+ userId: ctx.user.id,
+ timeZone: ctx.user.timeZone,
+ defaultScheduleId: ctx.user.defaultScheduleId,
});
-
- if (!schedule) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- });
- }
- const isCurrentUserPartOfTeam = hasReadPermissionsForUserId({
- ctx,
- input: { memberId: schedule?.userId },
- });
-
- const isCurrentUserOwner = schedule?.userId === user.id;
-
- if (!isCurrentUserPartOfTeam && !isCurrentUserOwner) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- });
- }
-
- const timeZone = schedule.timeZone || user.timeZone;
-
- const schedulesCount = await prisma.schedule.count({
- where: {
- userId: user.id,
- },
- });
- // disabling utc casting while fetching WorkingHours
- return {
- id: schedule.id,
- name: schedule.name,
- isManaged: schedule.userId !== user.id,
- workingHours: transformWorkingHoursForAtom(schedule),
- schedule: schedule.availability,
- availability: transformAvailabilityForAtom(schedule),
- timeZone,
- dateOverrides: transformDateOverridesForAtom(schedule, timeZone),
- isDefault: !input.scheduleId || user.defaultScheduleId === schedule.id,
- isLastSchedule: schedulesCount <= 1,
- readOnly: schedule.userId !== user.id && !input.isManagedEventType,
- };
};