diff --git a/apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.e2e-spec.ts b/apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.e2e-spec.ts index f452d47cae..3a7283f982 100644 --- a/apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.e2e-spec.ts +++ b/apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.e2e-spec.ts @@ -468,7 +468,7 @@ describe("Slots 2024-04-15 Endpoints", () => { it("should do a booking and slot should not be available at that time", async () => { const startTime = "2050-09-05T11:00:00.000Z"; const booking = await bookingsRepositoryFixture.create({ - uid: `booking-uid-${randomString()}`, + uid: `booking-uid-${eventTypeId}`, title: "booking title", startTime, endTime: "2050-09-05T12:00:00.000Z", @@ -515,7 +515,7 @@ describe("Slots 2024-04-15 Endpoints", () => { it("should do a booking for seated event and slot should show attendees count and bookingUid", async () => { const startTime = "2050-09-05T11:00:00.000Z"; const booking = await bookingsRepositoryFixture.create({ - uid: `booking-uid-${randomString()}`, + uid: `booking-uid-seated-${seatedEventTypeId}-${randomString()}`, title: "booking title", startTime, endTime: "2050-09-05T12:00:00.000Z", @@ -549,7 +549,7 @@ describe("Slots 2024-04-15 Endpoints", () => { }); bookingSeatsRepositoryFixture.create({ - referenceUid: randomString(), + referenceUid: `seat-${randomString()}`, data: {}, booking: { connect: { @@ -598,7 +598,7 @@ describe("Slots 2024-04-15 Endpoints", () => { it("should do a booking for seated event and slot should show attendees count and bookingUid in range format", async () => { const startTime = "2050-09-05T11:00:00.000Z"; const booking = await bookingsRepositoryFixture.create({ - uid: `booking-uid-${randomString()}`, + uid: `booking-uid-seated-${seatedEventTypeId}-${randomString()}`, title: "booking title", startTime, endTime: "2050-09-05T12:00:00.000Z", @@ -632,7 +632,7 @@ describe("Slots 2024-04-15 Endpoints", () => { }); bookingSeatsRepositoryFixture.create({ - referenceUid: randomString(), + referenceUid: `seat-${randomString()}`, data: {}, booking: { connect: { diff --git a/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/e2e/user-event-type-slots.controller.e2e-spec.ts b/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/e2e/user-event-type-slots.controller.e2e-spec.ts index 0bf0f370f7..3e9d3c99cc 100644 --- a/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/e2e/user-event-type-slots.controller.e2e-spec.ts +++ b/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/e2e/user-event-type-slots.controller.e2e-spec.ts @@ -787,7 +787,7 @@ describe("Slots 2024-09-04 Endpoints", () => { it("should do a booking and slot should not be available at that time", async () => { const startTime = "2050-09-05T11:00:00.000Z"; const booking = await bookingsRepositoryFixture.create({ - uid: `booking-uid-${randomString()}`, + uid: `booking-uid-${eventTypeId}`, title: "booking title", startTime, endTime: "2050-09-05T12:00:00.000Z", @@ -832,7 +832,7 @@ describe("Slots 2024-09-04 Endpoints", () => { it("should do a booking for seated event and slot should show attendees count and bookingUid", async () => { const startTime = "2050-09-05T11:00:00.000Z"; const booking = await bookingsRepositoryFixture.create({ - uid: `booking-uid-${randomString()}`, + uid: `booking-uid-seated-${seatedEventType.id}-${randomString()}`, title: "booking title", startTime, endTime: "2050-09-05T12:00:00.000Z", @@ -866,7 +866,7 @@ describe("Slots 2024-09-04 Endpoints", () => { }); bookingSeatsRepositoryFixture.create({ - referenceUid: randomString(), + referenceUid: `seat-${randomString()}`, data: {}, booking: { connect: { @@ -961,7 +961,7 @@ describe("Slots 2024-09-04 Endpoints", () => { it("should do a booking for seated event and slot should show attendees count and bookingUid and return range format", async () => { const startTime = "2050-09-05T11:00:00.000Z"; const booking = await bookingsRepositoryFixture.create({ - uid: `booking-uid-${randomString()}`, + uid: `booking-uid-seated-range-${seatedEventType.id}-${randomString()}`, title: "booking title", startTime, endTime: "2050-09-05T12:00:00.000Z", @@ -995,7 +995,7 @@ describe("Slots 2024-09-04 Endpoints", () => { }); bookingSeatsRepositoryFixture.create({ - referenceUid: randomString(), + referenceUid: `seat-${randomString()}`, data: {}, booking: { connect: { @@ -1348,7 +1348,8 @@ describe("Slots 2024-09-04 Endpoints", () => { advanceTo(newDate); const slotDuration = 60; - const slotStartTime = "2050-09-05T10:00:00.000Z"; + // Use 2050-09-06 to avoid overlap with other tests that reserve 2050-09-05 + const slotStartTime = "2050-09-06T09:00:00.000Z"; const reserveResponse = await request(app.getHttpServer()) .post(`/v2/slots/reservations`) .send({ @@ -1377,7 +1378,7 @@ describe("Slots 2024-09-04 Endpoints", () => { const response = await request(app.getHttpServer()) .get( - `/v2/slots?eventTypeId=${variableLengthEventType.id}&start=2050-09-05&end=2050-09-09&duration=60` + `/v2/slots?eventTypeId=${variableLengthEventType.id}&start=2050-09-05&end=2050-09-10&duration=60` ) .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) .expect(200); @@ -1390,10 +1391,10 @@ describe("Slots 2024-09-04 Endpoints", () => { const days = Object.keys(slots); expect(days.length).toEqual(5); - const expectedSlotsUTC2050_09_05 = expectedSlotsUTC["2050-09-05"].filter( + const expectedSlotsUTC2050_09_06 = expectedSlotsUTC["2050-09-06"].filter( (slot) => slot.start !== slotStartTime ); - expect(slots).toEqual({ ...expectedSlotsUTC, "2050-09-05": expectedSlotsUTC2050_09_05 }); + expect(slots).toEqual({ ...expectedSlotsUTC, "2050-09-06": expectedSlotsUTC2050_09_06 }); const dbSlot = await selectedSlotRepositoryFixture.getByUid( responseReservedVariableSlot.reservationUid @@ -1409,7 +1410,7 @@ describe("Slots 2024-09-04 Endpoints", () => { }); it("request slot contains already existing reserved slot", async () => { - // Try to reserve 9:45-12:45 when 10:00-11:00 is taken + // Try to reserve 8:45-11:45 when 09:00-10:00 on 2050-09-06 is taken const newSlotStart = DateTime.fromISO(responseReservedVariableSlot.slotStart) .minus({ minutes: 15 }) .toISO(); diff --git a/apps/api/v2/src/modules/workflows/workflows.repository.ts b/apps/api/v2/src/modules/workflows/workflows.repository.ts index f8be74f9df..4a341eb32d 100644 --- a/apps/api/v2/src/modules/workflows/workflows.repository.ts +++ b/apps/api/v2/src/modules/workflows/workflows.repository.ts @@ -114,7 +114,11 @@ export class WorkflowsRepository { ) { await updateWorkflow({ ctx: { - user: { ...user, locale: user?.locale ?? "en" }, + user: { + ...user, + locale: user?.locale ?? "en", + organizationId: user.profiles?.[0]?.organization?.id ?? null, + }, prisma: this.dbWrite.prisma as unknown as PrismaClient, }, input: data, @@ -132,7 +136,11 @@ export class WorkflowsRepository { ) { await updateWorkflow({ ctx: { - user: { ...user, locale: user?.locale ?? "en" }, + user: { + ...user, + locale: user?.locale ?? "en", + organizationId: user.profiles?.[0]?.organization?.id ?? null, + }, prisma: this.dbWrite.prisma as unknown as PrismaClient, }, input: data, diff --git a/apps/web/modules/ee/workflows/components/WorkflowDetailsPage.tsx b/apps/web/modules/ee/workflows/components/WorkflowDetailsPage.tsx index f564e5490a..8c40078885 100644 --- a/apps/web/modules/ee/workflows/components/WorkflowDetailsPage.tsx +++ b/apps/web/modules/ee/workflows/components/WorkflowDetailsPage.tsx @@ -178,6 +178,8 @@ export default function WorkflowDetailsPage(props: Props) { verifiedAt: SCANNING_WORKFLOW_STEPS ? null : new Date(), agentId: null, inboundAgentId: null, + autoTranslateEnabled: false, + sourceLocale: null, }; steps?.push(step); form.setValue("steps", steps); diff --git a/apps/web/modules/ee/workflows/components/WorkflowStepContainer.tsx b/apps/web/modules/ee/workflows/components/WorkflowStepContainer.tsx index 150b7a89fc..cbd1bbbf41 100644 --- a/apps/web/modules/ee/workflows/components/WorkflowStepContainer.tsx +++ b/apps/web/modules/ee/workflows/components/WorkflowStepContainer.tsx @@ -1,23 +1,22 @@ -import { type TFunction } from "i18next"; +import type { TFunction } from "i18next"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import type { Dispatch, SetStateAction } from "react"; -import { useEffect, useRef, useState, useCallback } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import type { UseFormReturn } from "react-hook-form"; import { Controller, useWatch } from "react-hook-form"; import "react-phone-number-input/style.css"; import type { RetellAgentWithDetails } from "@calcom/features/calAIPhone/providers/retellAI"; import { Dialog } from "@calcom/features/components/controlled-dialog"; -import PhoneInput from "@calcom/web/components/phone-input"; import { - isSMSAction, - isWhatsappAction, getTemplateBodyForAction, - shouldScheduleEmailReminder, - isSMSOrWhatsappAction, + hasCalAIAction, isCalAIAction, isFormTrigger, - hasCalAIAction, + isSMSAction, + isSMSOrWhatsappAction, + isWhatsappAction, + shouldScheduleEmailReminder, } from "@calcom/features/ee/workflows/lib/actionHelperFunctions"; import { DYNAMIC_TEXT_VARIABLES } from "@calcom/features/ee/workflows/lib/constants"; import { @@ -27,6 +26,7 @@ import { import emailRatingTemplate from "@calcom/features/ee/workflows/lib/reminders/templates/emailRatingTemplate"; import emailReminderTemplate from "@calcom/features/ee/workflows/lib/reminders/templates/emailReminderTemplate"; import type { FormValues } from "@calcom/features/ee/workflows/lib/types"; +import PhoneInput from "@calcom/web/components/phone-input"; import "@calcom/features/ee/workflows/style/styles.css"; import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; import { formatPhoneNumber } from "@calcom/lib/formatPhoneNumber"; @@ -48,7 +48,7 @@ import { trpc } from "@calcom/trpc/react"; import classNames from "@calcom/ui/classNames"; import { Badge, InfoBadge } from "@calcom/ui/components/badge"; import { Button } from "@calcom/ui/components/button"; -import { DialogContent, DialogFooter, DialogClose } from "@calcom/ui/components/dialog"; +import { DialogClose, DialogContent, DialogFooter } from "@calcom/ui/components/dialog"; import { Dropdown, DropdownItem, @@ -56,26 +56,26 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@calcom/ui/components/dropdown"; -import { AddVariablesDropdown } from "@calcom/ui/components/editor"; -import { Editor } from "@calcom/ui/components/editor"; -import { CheckboxField } from "@calcom/ui/components/form"; -import { EmailField } from "@calcom/ui/components/form"; -import { TextArea } from "@calcom/ui/components/form"; -import { Input } from "@calcom/ui/components/form"; -import { Label } from "@calcom/ui/components/form"; -import { TextField } from "@calcom/ui/components/form"; -import { Select } from "@calcom/ui/components/form"; -import { MultiSelectCheckbox } from "@calcom/ui/components/form"; +import { AddVariablesDropdown, Editor } from "@calcom/ui/components/editor"; import type { MultiSelectCheckboxesOptionType as Option } from "@calcom/ui/components/form"; +import { + CheckboxField, + EmailField, + Input, + Label, + MultiSelectCheckbox, + Select, + TextArea, + TextField, +} from "@calcom/ui/components/form"; import { CircleHelpIcon, InfoIcon, PhoneIcon } from "@coss/ui/icons"; import { SkeletonText } from "@calcom/ui/components/skeleton"; import { showToast } from "@calcom/ui/components/toast"; -import { useHasPaidPlan, useHasActiveTeamPlan } from "@calcom/web/modules/billing/hooks/useHasPaidPlan"; - +import { useHasActiveTeamPlan, useHasPaidPlan } from "@calcom/web/modules/billing/hooks/useHasPaidPlan"; +import { AgentConfigurationSheet }from "./agent-configuration/AgentConfigurationSheet"; import { TestPhoneCallDialog } from "./TestPhoneCallDialog"; import { TimeTimeUnitInput } from "./TimeTimeUnitInput"; import { WebCallDialog } from "./WebCallDialog"; -import { AgentConfigurationSheet } from "./agent-configuration/AgentConfigurationSheet"; type User = RouterOutputs["viewer"]["me"]["get"]; @@ -477,7 +477,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { const verifyEmailCodeMutation = trpc.viewer.workflows.verifyEmailCode.useMutation({ onSuccess: (isVerified) => { showToast(isVerified ? t("verified_successfully") : t("wrong_code"), "success"); - setEmailVerified(true); + setEmailVerified(isVerified); if ( step && form?.formState?.errors?.steps && @@ -800,6 +800,15 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { form.setValue(`steps.${step.stepNumber - 1}.agentId`, null); } + if ( + val.value !== WorkflowActions.EMAIL_ATTENDEE && + val.value !== WorkflowActions.SMS_ATTENDEE + ) { + form.setValue(`steps.${step.stepNumber - 1}.autoTranslateEnabled`, false, { + shouldDirty: true, + }); + } + form.setValue(`steps.${step.stepNumber - 1}.sendTo`, null); form.clearErrors(`steps.${step.stepNumber - 1}.sendTo`); form.setValue(`steps.${step.stepNumber - 1}.action`, val.value, { @@ -1433,6 +1442,49 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { /> )} + {(step.action === WorkflowActions.EMAIL_ATTENDEE || + step.action === WorkflowActions.SMS_ATTENDEE) && ( +
+
+ ( + + form.setValue( + `steps.${step.stepNumber - 1}.autoTranslateEnabled`, + e.target.checked, + { shouldDirty: true } + ) + } + /> + )} + /> + {!props.user.organizationId && ( + + {t("upgrade_to_organizations")} + + )} +
+ {props.user.organizationId && + form.watch(`steps.${step.stepNumber - 1}.autoTranslateEnabled`) && ( +

+ {t("auto_translate_source_language_hint", { + language: new Intl.DisplayNames([i18n.language], { type: "language" }).of( + props.user.locale || "en" + ), + })} +

+ )} +
+ )} {!props.readOnly && (