diff --git a/apps/web/lib/apps/installation/[[...step]]/getServerSideProps.ts b/apps/web/lib/apps/installation/[[...step]]/getServerSideProps.ts index 6b6a97fce0..9d73729e27 100644 --- a/apps/web/lib/apps/installation/[[...step]]/getServerSideProps.ts +++ b/apps/web/lib/apps/installation/[[...step]]/getServerSideProps.ts @@ -94,6 +94,7 @@ const getEventTypes = async (userId: number, teamIds?: number[]) => { userId: true, destinationCalendar: true, bookingFields: true, + calVideoSettings: true, }); let eventTypeGroups: TEventTypeGroup[] | null = []; diff --git a/apps/web/lib/video/[uid]/getServerSideProps.ts b/apps/web/lib/video/[uid]/getServerSideProps.ts index c12cfa44e6..b220a0eaad 100644 --- a/apps/web/lib/video/[uid]/getServerSideProps.ts +++ b/apps/web/lib/video/[uid]/getServerSideProps.ts @@ -12,12 +12,62 @@ import { getCalVideoReference } from "@calcom/features/get-cal-video-reference"; import { CAL_VIDEO_MEETING_LINK_FOR_TESTING } from "@calcom/lib/constants"; import { isENVDev } from "@calcom/lib/env"; import { BookingRepository } from "@calcom/lib/server/repository/booking"; +import { EventTypeRepository } from "@calcom/lib/server/repository/eventType"; import { OrganizationRepository } from "@calcom/lib/server/repository/organization"; import { UserRepository } from "@calcom/lib/server/repository/user"; import prisma from "@calcom/prisma"; const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true }); +type CalVideoSettings = { + disableRecordingForGuests: boolean; + disableRecordingForOrganizer: boolean; +}; + +const shouldEnableRecordButton = ({ + hasTeamPlan, + calVideoSettings, + isOrganizer, +}: { + hasTeamPlan: boolean; + calVideoSettings?: CalVideoSettings | null; + isOrganizer: boolean; +}) => { + if (!hasTeamPlan) return false; + if (!calVideoSettings) return true; + + if (isOrganizer) { + return !calVideoSettings.disableRecordingForOrganizer; + } + + return !calVideoSettings.disableRecordingForGuests; +}; + +const checkIfUserIsHost = async ({ + booking, + sessionUserId, +}: { + booking: { + user: { id: number } | null; + eventTypeId: number | undefined; + }; + sessionUserId?: number; +}) => { + if (!sessionUserId) return false; + + if (!booking.eventTypeId) { + return booking.user?.id === sessionUserId; + } + + const eventType = await EventTypeRepository.findByIdWithUserAccess({ + id: booking.eventTypeId, + userId: sessionUserId, + }); + + // If eventType exists, it means user is either owner, host or user + return !eventType; +}; + export async function getServerSideProps(context: GetServerSidePropsContext) { const { req } = context; @@ -107,13 +157,20 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { }); const sessionUserId = !!session?.user?.impersonatedBy ? session.user.impersonatedBy.id : session?.user.id; + const isOrganizer = await checkIfUserIsHost({ + booking: { + eventTypeId: bookingObj.eventType?.id, + user: bookingObj.user, + }, + sessionUserId, + }); // set meetingPassword for guests - if (sessionUserId !== bookingObj.user?.id) { - const guestMeetingPassword = await generateGuestMeetingTokenFromOwnerMeetingToken( - videoReferencePassword, - sessionUserId - ); + if (!isOrganizer) { + const guestMeetingPassword = await generateGuestMeetingTokenFromOwnerMeetingToken({ + meetingToken: videoReferencePassword, + userId: sessionUserId, + }); bookingObj.references.forEach((bookRef) => { bookRef.meetingPassword = guestMeetingPassword; @@ -141,6 +198,12 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { ? await featureRepo.checkIfTeamHasFeature(profile.organizationId, "cal-video-log-in-overlay") : false; + const showRecordingButton = shouldEnableRecordButton({ + hasTeamPlan: !!hasTeamPlan, + calVideoSettings: bookingObj.eventType?.calVideoSettings, + isOrganizer: sessionUserId === bookingObj.user?.id, + }); + return { props: { meetingUrl: videoReference.meetingUrl ?? "", @@ -161,6 +224,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { calVideoLogo, displayLogInOverlay, loggedInUserName: sessionUserId ? session?.user?.name : undefined, + showRecordingButton, + rediectAttendeeToOnExit: isOrganizer + ? undefined + : bookingObj.eventType?.calVideoSettings?.redirectUrlOnExit, }, }; } diff --git a/apps/web/modules/apps/installation/[[...step]]/step-view.tsx b/apps/web/modules/apps/installation/[[...step]]/step-view.tsx index 73fa18c548..9b452f37bf 100644 --- a/apps/web/modules/apps/installation/[[...step]]/step-view.tsx +++ b/apps/web/modules/apps/installation/[[...step]]/step-view.tsx @@ -37,7 +37,13 @@ import { STEPS } from "~/apps/installation/[[...step]]/constants"; export type TEventType = EventTypeAppSettingsComponentProps["eventType"] & Pick< EventTypeModel, - "metadata" | "schedulingType" | "slug" | "requiresConfirmation" | "position" | "destinationCalendar" + | "metadata" + | "schedulingType" + | "slug" + | "requiresConfirmation" + | "position" + | "destinationCalendar" + | "calVideoSettings" > & { selected: boolean; locations: LocationFormValues["locations"]; diff --git a/apps/web/modules/videos/views/videos-single-view.tsx b/apps/web/modules/videos/views/videos-single-view.tsx index 280de021e4..f09bc0f19a 100644 --- a/apps/web/modules/videos/views/videos-single-view.tsx +++ b/apps/web/modules/videos/views/videos-single-view.tsx @@ -3,6 +3,7 @@ import type { DailyCall } from "@daily-co/daily-js"; import DailyIframe from "@daily-co/daily-js"; import { DailyProvider } from "@daily-co/daily-react"; +import { useDailyEvent } from "@daily-co/daily-react"; import { useState, useEffect, useRef } from "react"; import dayjs from "@calcom/dayjs"; @@ -33,6 +34,8 @@ export default function JoinCall(props: PageProps) { calVideoLogo, displayLogInOverlay, loggedInUserName, + showRecordingButton, + rediectAttendeeToOnExit, } = props; const [daily, setDaily] = useState(null); @@ -65,12 +68,16 @@ export default function JoinCall(props: PageProps) { ...(typeof meetingPassword === "string" && { token: meetingPassword }), ...(hasTeamPlan && { customTrayButtons: { - recording: { - label: "Record", - tooltip: "Start or stop recording", - iconPath: RECORDING_DEFAULT_ICON, - iconPathDarkMode: RECORDING_DEFAULT_ICON, - }, + ...(showRecordingButton + ? { + recording: { + label: "Record", + tooltip: "Start or stop recording", + iconPath: RECORDING_DEFAULT_ICON, + iconPathDarkMode: RECORDING_DEFAULT_ICON, + }, + } + : {}), transcription: { label: "Cal.ai", tooltip: "Transcription powered by AI", @@ -126,7 +133,7 @@ export default function JoinCall(props: PageProps) { {displayLogInOverlay && } - + ); } @@ -255,16 +262,23 @@ export function LogInOverlay(props: LogInOverlayProps) { interface VideoMeetingInfo { booking: PageProps["booking"]; + rediectAttendeeToOnExit?: string | null; } export function VideoMeetingInfo(props: VideoMeetingInfo) { const [open, setOpen] = useState(false); - const { booking } = props; + const { booking, rediectAttendeeToOnExit } = props; const { t } = useLocale(); const endTime = new Date(booking.endTime); const startTime = new Date(booking.startTime); + useDailyEvent("left-meeting", () => { + if (rediectAttendeeToOnExit) { + window.location.href = rediectAttendeeToOnExit; + } + }); + return ( <>