diff --git a/apps/web/lib/video/[uid]/getServerSideProps.ts b/apps/web/lib/video/[uid]/getServerSideProps.ts index c54e6e8b0b..ac5f08ec81 100644 --- a/apps/web/lib/video/[uid]/getServerSideProps.ts +++ b/apps/web/lib/video/[uid]/getServerSideProps.ts @@ -23,6 +23,7 @@ type CalVideoSettings = { disableRecordingForGuests: boolean; disableRecordingForOrganizer: boolean; enableAutomaticTranscription: boolean; + enableAutomaticRecordingForOrganizer: boolean; disableTranscriptionForGuests: boolean; disableTranscriptionForOrganizer: boolean; }; @@ -59,6 +60,21 @@ const shouldEnableAutomaticTranscription = ({ return !!calVideoSettings.enableAutomaticTranscription; }; +const shouldEnableAutomaticRecording = ({ + hasTeamPlan, + calVideoSettings, + isOrganizer, +}: { + hasTeamPlan: boolean; + calVideoSettings?: CalVideoSettings | null; + isOrganizer: boolean; +}) => { + if (!hasTeamPlan || !isOrganizer) return false; + if (!calVideoSettings) return false; + + return !!calVideoSettings.enableAutomaticRecordingForOrganizer; +}; + const shouldEnableTranscriptionButton = ({ hasTeamPlan, calVideoSettings, @@ -239,17 +255,24 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const showRecordingButton = shouldEnableRecordButton({ hasTeamPlan: !!hasTeamPlan, calVideoSettings: bookingObj.eventType?.calVideoSettings, - isOrganizer: sessionUserId === bookingObj.user?.id, + isOrganizer, }); const enableAutomaticTranscription = shouldEnableAutomaticTranscription({ hasTeamPlan: !!hasTeamPlan, calVideoSettings: bookingObj.eventType?.calVideoSettings, }); + + const enableAutomaticRecordingForOrganizer = shouldEnableAutomaticRecording({ + hasTeamPlan: !!hasTeamPlan, + calVideoSettings: bookingObj.eventType?.calVideoSettings, + isOrganizer, + }); + const showTranscriptionButton = shouldEnableTranscriptionButton({ hasTeamPlan: !!hasTeamPlan, calVideoSettings: bookingObj.eventType?.calVideoSettings, - isOrganizer: sessionUserId === bookingObj.user?.id, + isOrganizer, }); return { @@ -274,6 +297,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { loggedInUserName: sessionUserId ? session?.user?.name : undefined, showRecordingButton, enableAutomaticTranscription, + enableAutomaticRecordingForOrganizer, showTranscriptionButton, rediectAttendeeToOnExit: isOrganizer ? undefined diff --git a/apps/web/modules/videos/__tests__/cal-video-premium-features.test.tsx b/apps/web/modules/videos/__tests__/cal-video-premium-features.test.tsx new file mode 100644 index 0000000000..2970b7fae5 --- /dev/null +++ b/apps/web/modules/videos/__tests__/cal-video-premium-features.test.tsx @@ -0,0 +1,492 @@ +import type { DailyCall } from "@daily-co/daily-js"; +import { describe, it, expect, vi, beforeEach } from "vitest"; + +import { BUTTONS } from "../button-states"; +import { createCalVideoCallbacks } from "../cal-video-premium-features"; + +vi.mock("@calcom/lib/constants", () => ({ + TRANSCRIPTION_STARTED_ICON: "/transcription-started-icon.svg", + RECORDING_IN_PROGRESS_ICON: "/recording-in-progress-icon.svg", + TRANSCRIPTION_STOPPED_ICON: "/transcription-stopped-icon.svg", + RECORDING_DEFAULT_ICON: "/recording-default-icon.svg", +})); + +const mockDaily: DailyCall = { + startRecording: vi.fn(), + stopRecording: vi.fn(), + startTranscription: vi.fn(), + stopTranscription: vi.fn(), + updateCustomTrayButtons: vi.fn(), +} as unknown as DailyCall; + +const createMockRecording = (isRecording = false) => ({ + isRecording, +}); + +const createMockTranscription = (isTranscribing = false) => ({ + isTranscribing, +}); + +describe("CalVideoPremiumFeatures - End-to-End Callback Tests", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("onMeetingJoined", () => { + it("should start transcription automatically when enabled and not already transcribing", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(false), // not transcribing + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: true, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.onMeetingJoined(); + + expect(mockDaily.startTranscription).toHaveBeenCalledTimes(1); + expect(mockDaily.startRecording).not.toHaveBeenCalled(); + }); + + it("should start recording automatically when enabled and not already recording", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(false), // not recording + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: true, + }); + + callbacks.onMeetingJoined(); + + expect(mockDaily.startRecording).toHaveBeenCalledTimes(1); + expect(mockDaily.startRecording).toHaveBeenCalledWith({ + videoBitrate: 2000, + }); + expect(mockDaily.startTranscription).not.toHaveBeenCalled(); + }); + + it("should start both transcription and recording when both are enabled", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(false), + transcription: createMockTranscription(false), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: true, + enableAutomaticRecordingForOrganizer: true, + }); + + callbacks.onMeetingJoined(); + + expect(mockDaily.startTranscription).toHaveBeenCalledTimes(1); + expect(mockDaily.startRecording).toHaveBeenCalledTimes(1); + }); + + it("should not start transcription when already transcribing", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(true), // already transcribing + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: true, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.onMeetingJoined(); + + expect(mockDaily.startTranscription).not.toHaveBeenCalled(); + }); + + it("should not start recording when already recording", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(true), // already recording + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: true, + }); + + callbacks.onMeetingJoined(); + + expect(mockDaily.startRecording).not.toHaveBeenCalled(); + }); + }); + + describe("onCustomButtonClick - Recording Button", () => { + it("should start recording when click occurs on Recording button and recording is stopped", async () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(false), // not recording + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + await callbacks.onCustomButtonClick({ button_id: "recording" }); + + expect(mockDaily.startRecording).toHaveBeenCalledTimes(1); + expect(mockDaily.startRecording).toHaveBeenCalledWith({ + videoBitrate: 2000, + }); + expect(mockDaily.stopRecording).not.toHaveBeenCalled(); + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.WAIT_FOR_RECORDING_TO_START, + transcription: BUTTONS.START_TRANSCRIPTION, // current state included + }); + }); + + it("should stop recording when click occurs on Recording button and recording is started", async () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(true), // currently recording + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + await callbacks.onCustomButtonClick({ button_id: "recording" }); + + expect(mockDaily.stopRecording).toHaveBeenCalledTimes(1); + expect(mockDaily.startRecording).not.toHaveBeenCalled(); + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.WAIT_FOR_RECORDING_TO_STOP, + transcription: BUTTONS.START_TRANSCRIPTION, // current state included + }); + }); + }); + + describe("onCustomButtonClick - Transcription Button", () => { + it("should start transcription when click occurs on Transcription button and transcription is stopped", async () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(false), // not transcribing + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + await callbacks.onCustomButtonClick({ button_id: "transcription" }); + + expect(mockDaily.startTranscription).toHaveBeenCalledTimes(1); + expect(mockDaily.stopTranscription).not.toHaveBeenCalled(); + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.START_RECORDING, // current state included + transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_START, + }); + }); + + it("should stop transcription when click occurs on Transcription button and transcription is started", async () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(true), // currently transcribing + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + await callbacks.onCustomButtonClick({ button_id: "transcription" }); + + expect(mockDaily.stopTranscription).toHaveBeenCalledTimes(1); + expect(mockDaily.startTranscription).not.toHaveBeenCalled(); + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.START_RECORDING, // current state included + transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_STOP, + }); + }); + }); + + describe("onRecordingStarted", () => { + it("should update custom tray buttons to show stop recording button", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.onRecordingStarted(); + + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.STOP_RECORDING, + transcription: BUTTONS.START_TRANSCRIPTION, // current state included + }); + }); + }); + + describe("onRecordingStopped", () => { + it("should update custom tray buttons to show start recording button", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.onRecordingStopped(); + + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.START_RECORDING, + transcription: BUTTONS.START_TRANSCRIPTION, // current state included + }); + }); + }); + + describe("onTranscriptionStarted", () => { + it("should update custom tray buttons to show stop transcription button", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.onTranscriptionStarted(); + + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.START_RECORDING, // current state included + transcription: BUTTONS.STOP_TRANSCRIPTION, + }); + }); + }); + + describe("onTranscriptionStopped", () => { + it("should update custom tray buttons to show start transcription button", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.onTranscriptionStopped(); + + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.START_RECORDING, // current state included + transcription: BUTTONS.START_TRANSCRIPTION, + }); + }); + }); + + describe("toggleRecording", () => { + it("should start recording when not currently recording", async () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(false), // not recording + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + await callbacks.toggleRecording(); + + expect(mockDaily.startRecording).toHaveBeenCalledTimes(1); + expect(mockDaily.startRecording).toHaveBeenCalledWith({ + videoBitrate: 2000, + }); + expect(mockDaily.stopRecording).not.toHaveBeenCalled(); + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.WAIT_FOR_RECORDING_TO_START, + transcription: BUTTONS.START_TRANSCRIPTION, // current state included + }); + }); + + it("should stop recording when currently recording", async () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(true), // currently recording + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + await callbacks.toggleRecording(); + + expect(mockDaily.stopRecording).toHaveBeenCalledTimes(1); + expect(mockDaily.startRecording).not.toHaveBeenCalled(); + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.WAIT_FOR_RECORDING_TO_STOP, + transcription: BUTTONS.START_TRANSCRIPTION, // current state included + }); + }); + }); + + describe("toggleTranscription", () => { + it("should start transcription when not currently transcribing", async () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(false), // not transcribing + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + await callbacks.toggleTranscription(); + + expect(mockDaily.startTranscription).toHaveBeenCalledTimes(1); + expect(mockDaily.stopTranscription).not.toHaveBeenCalled(); + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.START_RECORDING, // current state included + transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_START, + }); + }); + + it("should stop transcription when currently transcribing", async () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(true), // currently transcribing + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + await callbacks.toggleTranscription(); + + expect(mockDaily.stopTranscription).toHaveBeenCalledTimes(1); + expect(mockDaily.startTranscription).not.toHaveBeenCalled(); + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.START_RECORDING, // current state included + transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_STOP, + }); + }); + }); + + describe("updateCustomTrayButtons", () => { + it("should update both recording and transcription buttons when both are shown", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(false), + transcription: createMockTranscription(false), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.updateCustomTrayButtons({ + recording: BUTTONS.STOP_RECORDING, + transcription: BUTTONS.STOP_TRANSCRIPTION, + }); + + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.STOP_RECORDING, + transcription: BUTTONS.STOP_TRANSCRIPTION, + }); + }); + + it("should update only recording button when transcription button is not shown", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(false), + transcription: createMockTranscription(false), + showRecordingButton: true, + showTranscriptionButton: false, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.updateCustomTrayButtons({ + recording: BUTTONS.STOP_RECORDING, + }); + + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.STOP_RECORDING, + }); + }); + + it("should update only transcription button when recording button is not shown", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(false), + transcription: createMockTranscription(false), + showRecordingButton: false, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.updateCustomTrayButtons({ + transcription: BUTTONS.STOP_TRANSCRIPTION, + }); + + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + transcription: BUTTONS.STOP_TRANSCRIPTION, + }); + }); + + it("should use current state when no override is provided", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(true), // currently recording + transcription: createMockTranscription(false), // not transcribing + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.updateCustomTrayButtons({}); + + expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({ + recording: BUTTONS.STOP_RECORDING, // because isRecording is true + transcription: BUTTONS.START_TRANSCRIPTION, // because isTranscribing is false + }); + }); + }); + + describe("startRecording", () => { + it("should call daily.startRecording with correct parameters", () => { + const callbacks = createCalVideoCallbacks({ + daily: mockDaily, + recording: createMockRecording(), + transcription: createMockTranscription(), + showRecordingButton: true, + showTranscriptionButton: true, + enableAutomaticTranscription: false, + enableAutomaticRecordingForOrganizer: false, + }); + + callbacks.startRecording(); + + expect(mockDaily.startRecording).toHaveBeenCalledTimes(1); + expect(mockDaily.startRecording).toHaveBeenCalledWith({ + videoBitrate: 2000, + }); + }); + }); +}); diff --git a/apps/web/modules/videos/button-states.ts b/apps/web/modules/videos/button-states.ts new file mode 100644 index 0000000000..17a0801e87 --- /dev/null +++ b/apps/web/modules/videos/button-states.ts @@ -0,0 +1,57 @@ +import { + TRANSCRIPTION_STARTED_ICON, + RECORDING_IN_PROGRESS_ICON, + TRANSCRIPTION_STOPPED_ICON, + RECORDING_DEFAULT_ICON, +} from "@calcom/lib/constants"; + +export const BUTTONS = { + STOP_TRANSCRIPTION: { + label: "Stop", + tooltip: "Stop transcription", + iconPath: TRANSCRIPTION_STARTED_ICON, + iconPathDarkMode: TRANSCRIPTION_STARTED_ICON, + }, + START_TRANSCRIPTION: { + label: "Cal.ai", + tooltip: "Transcription powered by AI", + iconPath: TRANSCRIPTION_STOPPED_ICON, + iconPathDarkMode: TRANSCRIPTION_STOPPED_ICON, + }, + WAIT_FOR_TRANSCRIPTION_TO_START: { + label: "Starting..", + tooltip: "Please wait while we start transcription", + iconPath: TRANSCRIPTION_STOPPED_ICON, + iconPathDarkMode: TRANSCRIPTION_STOPPED_ICON, + }, + WAIT_FOR_TRANSCRIPTION_TO_STOP: { + label: "Stopping..", + tooltip: "Please wait while we stop transcription", + iconPath: TRANSCRIPTION_STOPPED_ICON, + iconPathDarkMode: TRANSCRIPTION_STOPPED_ICON, + }, + START_RECORDING: { + label: "Record", + tooltip: "Start recording", + iconPath: RECORDING_DEFAULT_ICON, + iconPathDarkMode: RECORDING_DEFAULT_ICON, + }, + WAIT_FOR_RECORDING_TO_START: { + label: "Starting..", + tooltip: "Please wait while we start recording", + iconPath: RECORDING_DEFAULT_ICON, + iconPathDarkMode: RECORDING_DEFAULT_ICON, + }, + WAIT_FOR_RECORDING_TO_STOP: { + label: "Stopping..", + tooltip: "Please wait while we stop recording", + iconPath: RECORDING_DEFAULT_ICON, + iconPathDarkMode: RECORDING_DEFAULT_ICON, + }, + STOP_RECORDING: { + label: "Stop", + tooltip: "Stop recording", + iconPath: RECORDING_IN_PROGRESS_ICON, + iconPathDarkMode: RECORDING_IN_PROGRESS_ICON, + }, +}; diff --git a/apps/web/modules/videos/ai/ai-transcribe.tsx b/apps/web/modules/videos/cal-video-premium-features.tsx similarity index 51% rename from apps/web/modules/videos/ai/ai-transcribe.tsx rename to apps/web/modules/videos/cal-video-premium-features.tsx index 157b1d3ab0..93fb0897ef 100644 --- a/apps/web/modules/videos/ai/ai-transcribe.tsx +++ b/apps/web/modules/videos/cal-video-premium-features.tsx @@ -1,65 +1,9 @@ +import type { DailyCall } from "@daily-co/daily-js"; import { useTranscription, useRecording } from "@daily-co/daily-react"; import { useDaily, useDailyEvent } from "@daily-co/daily-react"; import React, { Fragment, useCallback, useRef, useState, useLayoutEffect, useEffect } from "react"; -import { - TRANSCRIPTION_STARTED_ICON, - RECORDING_IN_PROGRESS_ICON, - TRANSCRIPTION_STOPPED_ICON, - RECORDING_DEFAULT_ICON, -} from "@calcom/lib/constants"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; - -const BUTTONS = { - STOP_TRANSCRIPTION: { - label: "Stop", - tooltip: "Stop transcription", - iconPath: TRANSCRIPTION_STARTED_ICON, - iconPathDarkMode: TRANSCRIPTION_STARTED_ICON, - }, - START_TRANSCRIPTION: { - label: "Cal.ai", - tooltip: "Transcription powered by AI", - iconPath: TRANSCRIPTION_STOPPED_ICON, - iconPathDarkMode: TRANSCRIPTION_STOPPED_ICON, - }, - WAIT_FOR_TRANSCRIPTION_TO_START: { - label: "Starting..", - tooltip: "Please wait while we start transcription", - iconPath: TRANSCRIPTION_STOPPED_ICON, - iconPathDarkMode: TRANSCRIPTION_STOPPED_ICON, - }, - WAIT_FOR_TRANSCRIPTION_TO_STOP: { - label: "Stopping..", - tooltip: "Please wait while we stop transcription", - iconPath: TRANSCRIPTION_STOPPED_ICON, - iconPathDarkMode: TRANSCRIPTION_STOPPED_ICON, - }, - START_RECORDING: { - label: "Record", - tooltip: "Start recording", - iconPath: RECORDING_DEFAULT_ICON, - iconPathDarkMode: RECORDING_DEFAULT_ICON, - }, - WAIT_FOR_RECORDING_TO_START: { - label: "Starting..", - tooltip: "Please wait while we start recording", - iconPath: RECORDING_DEFAULT_ICON, - iconPathDarkMode: RECORDING_DEFAULT_ICON, - }, - WAIT_FOR_RECORDING_TO_STOP: { - label: "Stopping..", - tooltip: "Please wait while we stop recording", - iconPath: RECORDING_DEFAULT_ICON, - iconPathDarkMode: RECORDING_DEFAULT_ICON, - }, - STOP_RECORDING: { - label: "Stop", - tooltip: "Stop recording", - iconPath: RECORDING_IN_PROGRESS_ICON, - iconPathDarkMode: RECORDING_IN_PROGRESS_ICON, - }, -}; +import { BUTTONS } from "./button-states"; export type DailyCustomTrayButtonVisualState = "default" | "sidebar-open" | "active"; @@ -70,46 +14,180 @@ export interface DailyCustomTrayButton { tooltip: string; visualState?: DailyCustomTrayButtonVisualState; } -export const CalAiTranscribe = ({ + +type RecordingState = { + isRecording: boolean; +}; + +type TranscriptionState = { + isTranscribing: boolean; +}; + +type CalVideoCallbacksParams = { + daily: DailyCall | null; + recording: RecordingState | null; + transcription: TranscriptionState | null; + showRecordingButton: boolean; + showTranscriptionButton: boolean; + enableAutomaticTranscription: boolean; + enableAutomaticRecordingForOrganizer: boolean; +}; + +export const createCalVideoCallbacks = (params: CalVideoCallbacksParams) => { + const { + daily, + recording, + transcription, + showRecordingButton, + showTranscriptionButton, + enableAutomaticTranscription, + enableAutomaticRecordingForOrganizer, + } = params; + + const startRecording = () => { + daily?.startRecording({ + // 480p + videoBitrate: 2000, + }); + }; + + const updateCustomTrayButtons = ({ + recording: overrideRecording, + transcription: overrideTranscription, + }: { + recording?: DailyCustomTrayButton; + transcription?: DailyCustomTrayButton; + }) => { + const currentRecordingState = recording?.isRecording ? BUTTONS.STOP_RECORDING : BUTTONS.START_RECORDING; + const currentTranscriptionState = transcription?.isTranscribing + ? BUTTONS.STOP_TRANSCRIPTION + : BUTTONS.START_TRANSCRIPTION; + + daily?.updateCustomTrayButtons({ + ...(showRecordingButton + ? { + recording: overrideRecording ?? currentRecordingState, + } + : {}), + ...(showTranscriptionButton + ? { + transcription: overrideTranscription ?? currentTranscriptionState, + } + : {}), + }); + }; + + const onMeetingJoined = () => { + if (enableAutomaticTranscription && !transcription?.isTranscribing) { + daily?.startTranscription(); + } + if (enableAutomaticRecordingForOrganizer && !recording?.isRecording) { + startRecording(); + } + }; + + const onRecordingStarted = () => { + updateCustomTrayButtons({ + recording: BUTTONS.STOP_RECORDING, + }); + }; + + const onRecordingStopped = () => { + updateCustomTrayButtons({ + recording: BUTTONS.START_RECORDING, + }); + }; + + const onTranscriptionStarted = () => { + updateCustomTrayButtons({ + transcription: BUTTONS.STOP_TRANSCRIPTION, + }); + }; + + const onTranscriptionStopped = () => { + updateCustomTrayButtons({ + transcription: BUTTONS.START_TRANSCRIPTION, + }); + }; + + const toggleRecording = async () => { + if (recording?.isRecording) { + updateCustomTrayButtons({ + recording: BUTTONS.WAIT_FOR_RECORDING_TO_STOP, + }); + daily?.stopRecording(); + } else { + updateCustomTrayButtons({ + recording: BUTTONS.WAIT_FOR_RECORDING_TO_START, + }); + startRecording(); + } + }; + + const toggleTranscription = async () => { + if (transcription?.isTranscribing) { + updateCustomTrayButtons({ + transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_STOP, + }); + daily?.stopTranscription(); + } else { + updateCustomTrayButtons({ + transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_START, + }); + daily?.startTranscription(); + } + }; + + const onCustomButtonClick = async (ev: { button_id: string }) => { + if (ev?.button_id === "recording") { + toggleRecording(); + } else if (ev?.button_id === "transcription") { + toggleTranscription(); + } + }; + + return { + onMeetingJoined, + onRecordingStarted, + onRecordingStopped, + onTranscriptionStarted, + onTranscriptionStopped, + onCustomButtonClick, + toggleRecording, + toggleTranscription, + updateCustomTrayButtons, + startRecording, + }; +}; + +export const CalVideoPremiumFeatures = ({ showRecordingButton, enableAutomaticTranscription, + enableAutomaticRecordingForOrganizer, showTranscriptionButton, }: { showRecordingButton: boolean; enableAutomaticTranscription: boolean; + enableAutomaticRecordingForOrganizer: boolean; showTranscriptionButton: boolean; }) => { const daily = useDaily(); - const { t } = useLocale(); - const [transcript, setTranscript] = useState(""); - const [transcriptHeight, setTranscriptHeight] = useState(0); const transcriptRef = useRef(null); const transcription = useTranscription(); const recording = useRecording(); - const updateCustomTrayButtons = ({ + const callbacks = createCalVideoCallbacks({ + daily, recording, transcription, - }: { - recording: DailyCustomTrayButton; - transcription: DailyCustomTrayButton; - }) => { - daily?.updateCustomTrayButtons({ - ...(showRecordingButton - ? { - recording, - } - : {}), - ...(showTranscriptionButton - ? { - transcription, - } - : {}), - }); - }; + showRecordingButton, + showTranscriptionButton, + enableAutomaticTranscription, + enableAutomaticRecordingForOrganizer, + }); useDailyEvent( "app-message", @@ -119,94 +197,12 @@ export const CalAiTranscribe = ({ }, []) ); - useDailyEvent("joined-meeting", (ev) => { - if (enableAutomaticTranscription) { - daily?.startTranscription(); - updateCustomTrayButtons({ - recording: BUTTONS.START_RECORDING, - transcription: transcription?.isTranscribing - ? BUTTONS.STOP_TRANSCRIPTION - : BUTTONS.START_TRANSCRIPTION, - }); - } - }); - - useDailyEvent("transcription-started", (ev) => { - updateCustomTrayButtons({ - recording: recording?.isRecording ? BUTTONS.STOP_RECORDING : BUTTONS.START_RECORDING, - transcription: BUTTONS.STOP_TRANSCRIPTION, - }); - }); - - useDailyEvent("recording-started", (ev) => { - updateCustomTrayButtons({ - recording: BUTTONS.STOP_RECORDING, - transcription: transcription?.isTranscribing ? BUTTONS.STOP_TRANSCRIPTION : BUTTONS.START_TRANSCRIPTION, - }); - }); - - useDailyEvent("transcription-stopped", (ev) => { - updateCustomTrayButtons({ - recording: recording?.isRecording ? BUTTONS.STOP_RECORDING : BUTTONS.START_RECORDING, - transcription: BUTTONS.START_TRANSCRIPTION, - }); - }); - - useDailyEvent("recording-stopped", (ev) => { - updateCustomTrayButtons({ - recording: BUTTONS.START_RECORDING, - transcription: transcription?.isTranscribing ? BUTTONS.STOP_TRANSCRIPTION : BUTTONS.START_TRANSCRIPTION, - }); - }); - - const toggleRecording = async () => { - if (recording?.isRecording) { - updateCustomTrayButtons({ - recording: BUTTONS.WAIT_FOR_RECORDING_TO_STOP, - transcription: transcription?.isTranscribing - ? BUTTONS.STOP_TRANSCRIPTION - : BUTTONS.START_TRANSCRIPTION, - }); - await daily?.stopRecording(); - } else { - updateCustomTrayButtons({ - recording: BUTTONS.WAIT_FOR_RECORDING_TO_START, - transcription: transcription?.isTranscribing - ? BUTTONS.STOP_TRANSCRIPTION - : BUTTONS.START_TRANSCRIPTION, - }); - - await daily?.startRecording({ - // 480p - videoBitrate: 2000, - }); - } - }; - - const toggleTranscription = async () => { - if (transcription?.isTranscribing) { - updateCustomTrayButtons({ - recording: recording?.isRecording ? BUTTONS.STOP_RECORDING : BUTTONS.START_RECORDING, - transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_STOP, - }); - daily?.stopTranscription(); - } else { - updateCustomTrayButtons({ - recording: recording?.isRecording ? BUTTONS.STOP_RECORDING : BUTTONS.START_RECORDING, - transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_START, - }); - - daily?.startTranscription(); - } - }; - - useDailyEvent("custom-button-click", async (ev) => { - if (ev?.button_id === "recording") { - toggleRecording(); - } else if (ev?.button_id === "transcription") { - toggleTranscription(); - } - }); + useDailyEvent("joined-meeting", callbacks.onMeetingJoined); + useDailyEvent("transcription-started", callbacks.onTranscriptionStarted); + useDailyEvent("recording-started", callbacks.onRecordingStarted); + useDailyEvent("transcription-stopped", callbacks.onTranscriptionStopped); + useDailyEvent("recording-stopped", callbacks.onRecordingStopped); + useDailyEvent("custom-button-click", callbacks.onCustomButtonClick); useLayoutEffect(() => { const observer = new ResizeObserver((entries) => { diff --git a/apps/web/modules/videos/views/videos-single-view.tsx b/apps/web/modules/videos/views/videos-single-view.tsx index 5dcf2321c6..a7a67a1839 100644 --- a/apps/web/modules/videos/views/videos-single-view.tsx +++ b/apps/web/modules/videos/views/videos-single-view.tsx @@ -21,7 +21,7 @@ import { Icon } from "@calcom/ui/components/icon"; import type { getServerSideProps } from "@lib/video/[uid]/getServerSideProps"; -import { CalAiTranscribe } from "~/videos/ai/ai-transcribe"; +import { CalVideoPremiumFeatures } from "../cal-video-premium-features"; export type PageProps = inferSSRProps; @@ -37,6 +37,7 @@ export default function JoinCall(props: PageProps) { overrideName, showRecordingButton, enableAutomaticTranscription, + enableAutomaticRecordingForOrganizer, showTranscriptionButton, rediectAttendeeToOnExit, } = props; @@ -116,8 +117,9 @@ export default function JoinCall(props: PageProps) {
- diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index cbdf0db137..9cc19f8650 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -48,6 +48,7 @@ "calvideo_settings_description": "Configure video meeting preferences including recording permissions for hosts and attendees.", "disable_recording_for_guests": "Disable recording for guests", "enable_automatic_transcription": "Enable automatic transcription after joining the meeting", + "enable_automatic_recording": "Enable automatic recording after organizer joins the meeting", "video_options": "Video Options", "get_meeting_session_details": "Get Meeting Session Details", "meeting_session_details": "Meeting Session Details", diff --git a/packages/features/eventtypes/components/Locations.tsx b/packages/features/eventtypes/components/Locations.tsx index a71f2540cf..bcaf84085f 100644 --- a/packages/features/eventtypes/components/Locations.tsx +++ b/packages/features/eventtypes/components/Locations.tsx @@ -25,6 +25,7 @@ import ServerTrans from "@calcom/lib/components/ServerTrans"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import classNames from "@calcom/ui/classNames"; +import { UpgradeTeamsBadge } from "@calcom/ui/components/badge"; import { Button } from "@calcom/ui/components/button"; import { TextField } from "@calcom/ui/components/form"; import { SettingsToggle } from "@calcom/ui/components/form"; @@ -355,6 +356,7 @@ const Locations: React.FC = ({ labelClassName="text-sm leading-6 whitespace-normal break-words" checked={value} onCheckedChange={onChange} + Badge={} /> ); }} @@ -370,11 +372,30 @@ const Locations: React.FC = ({ labelClassName="text-sm leading-6 whitespace-normal break-words" checked={value} onCheckedChange={onChange} + Badge={} /> ); }} /> + {!isPlatform && ( + { + return ( + } + /> + ); + }} + /> + )} + = ({ labelClassName="text-sm leading-6 whitespace-normal break-words" checked={value} onCheckedChange={onChange} + Badge={} /> ); }} @@ -401,6 +423,7 @@ const Locations: React.FC = ({ labelClassName="text-sm leading-6 whitespace-normal break-words" checked={value} onCheckedChange={onChange} + Badge={} /> ); }} @@ -417,6 +440,7 @@ const Locations: React.FC = ({ labelClassName="text-sm leading-6 whitespace-normal break-words" checked={value} onCheckedChange={onChange} + Badge={} /> ); }} diff --git a/packages/features/eventtypes/lib/types.ts b/packages/features/eventtypes/lib/types.ts index 782fa2c827..defa1c972b 100644 --- a/packages/features/eventtypes/lib/types.ts +++ b/packages/features/eventtypes/lib/types.ts @@ -159,6 +159,7 @@ export type FormValues = { disableRecordingForOrganizer?: boolean; disableRecordingForGuests?: boolean; enableAutomaticTranscription?: boolean; + enableAutomaticRecordingForOrganizer?: boolean; disableTranscriptionForGuests?: boolean; disableTranscriptionForOrganizer?: boolean; redirectUrlOnExit?: string; diff --git a/packages/lib/server/repository/booking.ts b/packages/lib/server/repository/booking.ts index 2de6d33462..861f323bc9 100644 --- a/packages/lib/server/repository/booking.ts +++ b/packages/lib/server/repository/booking.ts @@ -398,6 +398,7 @@ export class BookingRepository { disableRecordingForGuests: true, disableRecordingForOrganizer: true, enableAutomaticTranscription: true, + enableAutomaticRecordingForOrganizer: true, disableTranscriptionForGuests: true, disableTranscriptionForOrganizer: true, redirectUrlOnExit: true, diff --git a/packages/lib/server/repository/calVideoSettings.ts b/packages/lib/server/repository/calVideoSettings.ts index fa535c4394..7ce89725d9 100644 --- a/packages/lib/server/repository/calVideoSettings.ts +++ b/packages/lib/server/repository/calVideoSettings.ts @@ -7,6 +7,35 @@ export class CalVideoSettingsRepository { }); } + static async createCalVideoSettings({ + eventTypeId, + calVideoSettings, + }: { + eventTypeId: number; + calVideoSettings: { + disableRecordingForGuests?: boolean | null; + disableRecordingForOrganizer?: boolean | null; + enableAutomaticTranscription?: boolean | null; + enableAutomaticRecordingForOrganizer?: boolean | null; + disableTranscriptionForGuests?: boolean | null; + disableTranscriptionForOrganizer?: boolean | null; + redirectUrlOnExit?: string | null; + }; + }) { + return await prisma.calVideoSettings.create({ + data: { + disableRecordingForGuests: calVideoSettings.disableRecordingForGuests ?? false, + disableRecordingForOrganizer: calVideoSettings.disableRecordingForOrganizer ?? false, + enableAutomaticTranscription: calVideoSettings.enableAutomaticTranscription ?? false, + enableAutomaticRecordingForOrganizer: calVideoSettings.enableAutomaticRecordingForOrganizer ?? false, + disableTranscriptionForGuests: calVideoSettings.disableTranscriptionForGuests ?? false, + disableTranscriptionForOrganizer: calVideoSettings.disableTranscriptionForOrganizer ?? false, + redirectUrlOnExit: calVideoSettings.redirectUrlOnExit ?? null, + eventTypeId, + }, + }); + } + static async createOrUpdateCalVideoSettings({ eventTypeId, calVideoSettings, @@ -18,6 +47,7 @@ export class CalVideoSettingsRepository { disableTranscriptionForGuests?: boolean | null; disableTranscriptionForOrganizer?: boolean | null; enableAutomaticTranscription?: boolean | null; + enableAutomaticRecordingForOrganizer?: boolean | null; redirectUrlOnExit?: string | null; }; }) { @@ -27,6 +57,7 @@ export class CalVideoSettingsRepository { disableRecordingForGuests: calVideoSettings.disableRecordingForGuests ?? false, disableRecordingForOrganizer: calVideoSettings.disableRecordingForOrganizer ?? false, enableAutomaticTranscription: calVideoSettings.enableAutomaticTranscription ?? false, + enableAutomaticRecordingForOrganizer: calVideoSettings.enableAutomaticRecordingForOrganizer ?? false, disableTranscriptionForGuests: calVideoSettings.disableTranscriptionForGuests ?? false, disableTranscriptionForOrganizer: calVideoSettings.disableTranscriptionForOrganizer ?? false, redirectUrlOnExit: calVideoSettings.redirectUrlOnExit ?? null, @@ -36,6 +67,7 @@ export class CalVideoSettingsRepository { disableRecordingForGuests: calVideoSettings.disableRecordingForGuests ?? false, disableRecordingForOrganizer: calVideoSettings.disableRecordingForOrganizer ?? false, enableAutomaticTranscription: calVideoSettings.enableAutomaticTranscription ?? false, + enableAutomaticRecordingForOrganizer: calVideoSettings.enableAutomaticRecordingForOrganizer ?? false, disableTranscriptionForGuests: calVideoSettings.disableTranscriptionForGuests ?? false, disableTranscriptionForOrganizer: calVideoSettings.disableTranscriptionForOrganizer ?? false, redirectUrlOnExit: calVideoSettings.redirectUrlOnExit ?? null, diff --git a/packages/lib/server/repository/eventType.ts b/packages/lib/server/repository/eventType.ts index b725b6b0fc..a88a230d02 100644 --- a/packages/lib/server/repository/eventType.ts +++ b/packages/lib/server/repository/eventType.ts @@ -709,6 +709,7 @@ export class EventTypeRepository { disableRecordingForGuests: true, disableRecordingForOrganizer: true, enableAutomaticTranscription: true, + enableAutomaticRecordingForOrganizer: true, disableTranscriptionForGuests: true, disableTranscriptionForOrganizer: true, redirectUrlOnExit: true, diff --git a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts index 59ea173f6c..e9279a2c32 100644 --- a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts +++ b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts @@ -177,6 +177,7 @@ export const useEventTypeForm = ({ disableRecordingForOrganizer: z.boolean().nullable(), disableRecordingForGuests: z.boolean().nullable(), enableAutomaticTranscription: z.boolean().nullable(), + enableAutomaticRecordingForOrganizer: z.boolean().nullable(), disableTranscriptionForGuests: z.boolean().nullable(), disableTranscriptionForOrganizer: z.boolean().nullable(), }) diff --git a/packages/prisma/migrations/20250617092501_add_automatic_recording/migration.sql b/packages/prisma/migrations/20250617092501_add_automatic_recording/migration.sql new file mode 100644 index 0000000000..e59d09fe16 --- /dev/null +++ b/packages/prisma/migrations/20250617092501_add_automatic_recording/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "CalVideoSettings" ADD COLUMN "enableAutomaticRecordingForOrganizer" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 17fafe7e4c..b4c1698dff 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -72,15 +72,15 @@ model CalVideoSettings { eventTypeId Int @id eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade) - disableRecordingForOrganizer Boolean @default(false) - disableRecordingForGuests Boolean @default(false) - enableAutomaticTranscription Boolean @default(false) - redirectUrlOnExit String? - disableTranscriptionForGuests Boolean @default(false) - disableTranscriptionForOrganizer Boolean @default(false) - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + disableRecordingForOrganizer Boolean @default(false) + disableRecordingForGuests Boolean @default(false) + enableAutomaticTranscription Boolean @default(false) + enableAutomaticRecordingForOrganizer Boolean @default(false) + redirectUrlOnExit String? + disableTranscriptionForGuests Boolean @default(false) + disableTranscriptionForOrganizer Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model EventType { diff --git a/packages/trpc/server/routers/viewer/eventTypes/duplicate.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/duplicate.handler.ts index 56dc2dfecf..db19e16d81 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/duplicate.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/duplicate.handler.ts @@ -1,6 +1,7 @@ import { Prisma } from "@prisma/client"; import { generateHashedLink } from "@calcom/lib/generateHashedLink"; +import { CalVideoSettingsRepository } from "@calcom/lib/server/repository/calVideoSettings"; import { EventTypeRepository } from "@calcom/lib/server/repository/eventType"; import { prisma } from "@calcom/prisma"; @@ -44,6 +45,17 @@ export const duplicateHandler = async ({ ctx, input }: DuplicateOptions) => { webhooks: true, hashedLink: true, destinationCalendar: true, + calVideoSettings: { + select: { + disableRecordingForOrganizer: true, + disableRecordingForGuests: true, + enableAutomaticTranscription: true, + enableAutomaticRecordingForOrganizer: true, + redirectUrlOnExit: true, + disableTranscriptionForGuests: true, + disableTranscriptionForOrganizer: true, + }, + }, }, }); @@ -95,6 +107,7 @@ export const duplicateHandler = async ({ ctx, input }: DuplicateOptions) => { secondaryEmailId, instantMeetingScheduleId: _instantMeetingScheduleId, restrictionScheduleId: _restrictionScheduleId, + calVideoSettings, ...rest } = eventType; @@ -179,6 +192,13 @@ export const duplicateHandler = async ({ ctx, input }: DuplicateOptions) => { }); } + if (calVideoSettings) { + await CalVideoSettingsRepository.createCalVideoSettings({ + eventTypeId: newEventType.id, + calVideoSettings, + }); + } + if (workflows.length > 0) { const relationCreateData = workflows.map((workflow) => { return { eventTypeId: newEventType.id, workflowId: workflow.workflowId }; diff --git a/packages/trpc/server/routers/viewer/eventTypes/types.ts b/packages/trpc/server/routers/viewer/eventTypes/types.ts index f7e0ae2464..2731d60b66 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/types.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/types.ts @@ -27,12 +27,13 @@ const aiPhoneCallConfig = z const calVideoSettingsSchema = z .object({ - disableRecordingForGuests: z.boolean().optional().nullable(), - disableRecordingForOrganizer: z.boolean().optional().nullable(), - enableAutomaticTranscription: z.boolean().optional().nullable(), - disableTranscriptionForGuests: z.boolean().optional().nullable(), - disableTranscriptionForOrganizer: z.boolean().optional().nullable(), - redirectUrlOnExit: z.string().url().optional().nullable(), + disableRecordingForGuests: z.boolean().nullish(), + disableRecordingForOrganizer: z.boolean().nullish(), + enableAutomaticTranscription: z.boolean().nullish(), + enableAutomaticRecordingForOrganizer: z.boolean().nullish(), + disableTranscriptionForGuests: z.boolean().nullish(), + disableTranscriptionForOrganizer: z.boolean().nullish(), + redirectUrlOnExit: z.string().url().nullish(), }) .optional() .nullable(); diff --git a/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts index 4136c96074..683b85427a 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts @@ -26,6 +26,7 @@ import { TRPCError } from "@trpc/server"; import type { TrpcSessionUser } from "../../../types"; import { setDestinationCalendarHandler } from "../../viewer/calendars/setDestinationCalendar.handler"; +import { hasTeamPlanHandler } from "../teams/hasTeamPlan.handler"; import type { TUpdateInputSchema } from "./update.schema"; import { ensureUniqueBookingFields, @@ -130,6 +131,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { disableRecordingForOrganizer: true, disableRecordingForGuests: true, enableAutomaticTranscription: true, + enableAutomaticRecordingForOrganizer: true, disableTranscriptionForGuests: true, disableTranscriptionForOrganizer: true, redirectUrlOnExit: true, @@ -598,10 +600,16 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { } if (calVideoSettings) { - await CalVideoSettingsRepository.createOrUpdateCalVideoSettings({ - eventTypeId: id, - calVideoSettings, + const { hasTeamPlan } = await hasTeamPlanHandler({ + ctx: { user: ctx.user }, }); + + if (hasTeamPlan) { + await CalVideoSettingsRepository.createOrUpdateCalVideoSettings({ + eventTypeId: id, + calVideoSettings, + }); + } } const parsedEventTypeLocations = eventTypeLocations.safeParse(eventType.locations ?? []); diff --git a/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts b/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts index f5c5316594..08296a5841 100644 --- a/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts @@ -1,19 +1,16 @@ import { prisma } from "@calcom/prisma"; -import type { TrpcSessionUser } from "@calcom/trpc/server/types"; type HasTeamPlanOptions = { ctx: { - user: NonNullable; + user: { id: number }; }; }; -export const hasTeamPlanHandler = async ({ ctx }: HasTeamPlanOptions) => { - const userId = ctx.user.id; - +export const hasTeamPlanHandler = async ({ ctx: { user } }: HasTeamPlanOptions) => { const hasTeamPlan = await prisma.membership.findFirst({ where: { accepted: true, - userId, + userId: user.id, team: { slug: { not: null, diff --git a/packages/ui/components/form/switch/SettingsToggle.tsx b/packages/ui/components/form/switch/SettingsToggle.tsx index 1c5a527f41..04c3dae35f 100644 --- a/packages/ui/components/form/switch/SettingsToggle.tsx +++ b/packages/ui/components/form/switch/SettingsToggle.tsx @@ -106,7 +106,7 @@ export function SettingsToggle({
{description &&

{description}

}