diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 5ac31a6345..9cdbda20c3 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1150,13 +1150,13 @@ "current_username": "Current username", "example_1": "Example 1", "example_2": "Example 2", - "additional_input_label": "Additional Input Label", + "booking_question_identifier": "Booking Question Identifier", "company_size": "Company size", "what_help_needed": "What do you need help with?", "variable_format": "Variable format", "webhook_subscriber_url_reserved": "Webhook subscriber url is already defined", "custom_input_as_variable_info": "Ignore all special characters of the additional input label (use only letters and numbers), use uppercase for all letters and replace whitespaces with underscores.", - "using_additional_inputs_as_variables": "How do I use Additional Inputs as variables?", + "using_booking_questions_as_variables": "How do I use booking questions as variables?", "download_desktop_app": "Download desktop app", "set_ping_link": "Set Ping link", "rate_limit_exceeded": "Rate limit exceeded", @@ -1382,7 +1382,7 @@ "add_limit": "Add Limit", "team_name_required": "Team name required", "show_attendees": "Share attendee information between guests", - "how_additional_inputs_as_variables": "How to use Additional Inputs as Variables", + "how_booking_questions_as_variables": "How to use booking questions as variables?", "format": "Format", "uppercase_for_letters": "Use uppercase for all letters", "replace_whitespaces_underscores": "Replace whitespaces with underscores", @@ -1397,7 +1397,7 @@ "billing_help_title": "Need anything else?", "billing_help_description": "If you need any further help with billing, our support team are here to help.", "billing_help_cta": "Contact support", - "ignore_special_characters": "Ignore special characters in your Additional Input label. Use only letters and numbers", + "ignore_special_characters_booking_questions": "Ignore special characters in your booking question identifier. Use only letters and numbers", "retry": "Retry", "fetching_calendars_error": "There was a problem fetching your calendars. Please <1>try again1> or reach out to customer support.", "calendar_connection_fail": "Calendar connection failed", @@ -1705,9 +1705,17 @@ "verification_code": "Verification code", "can_you_try_again": "Can you try again with a different time?", "verify": "Verify", + "timezone_variable": "Timezone", + "timezone_info": "The timezone of the person receiving", + "event_end_time_variable": "Event end time", + "event_end_time_info": "The event end time", + "cancel_url_variable": "Cancel URL", + "cancel_url_info": "The URL to cancel the booking", + "reschedule_url_variable": "Reschedule URL", + "reschedule_url_info": "The URL to reschedule the booking", + "invalid_event_name_variables": "There is an invalid variable in your event name", "select_all": "Select All", "default_conferencing_bulk_title": "Bulk update existing event types", - "invalid_event_name_variables": "There is an invalid variable in your event name", "members_default_schedule": "Member's default schedule", "set_by_admin": "Set by team admin", "members_default_location": "Member's default location", @@ -1769,6 +1777,7 @@ "collect_no_show_fee": "Collect no-show fee", "no_show_fee_charged": "No-show fee charged", "insights": "Insights", + "testing_workflow_info_message": "When testing this workflow, be aware that Emails and SMS can only be scheduled at least 1 hour in advance", "insights_no_data_found_for_filter": "No data found for the selected filter or selected dates.", "acknowledge_booking_no_show_fee": "I acknowledge that if I do not attend this event that a {{amount, currency}} no show fee will be applied to my card.", "card_details": "Card details", diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 49795f6af1..495bebe570 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -1,4 +1,4 @@ -import type { WebhookTriggerEvents, WorkflowReminder, Prisma } from "@prisma/client"; +import type { Prisma, WebhookTriggerEvents, WorkflowReminder } from "@prisma/client"; import { BookingStatus, MembershipRole, WorkflowMethods } from "@prisma/client"; import type { NextApiRequest } from "next"; @@ -58,6 +58,7 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine paid: true, eventType: { select: { + slug: true, owner: true, teamId: true, recurringEvent: true, @@ -334,7 +335,11 @@ async function handler(req: CustomRequest) { await sendCancelledReminders({ workflows: bookingToDelete.eventType?.workflows, smsReminderNumber: bookingToDelete.smsReminderNumber, - evt, + evt: { + ...evt, + ...{ eventType: { slug: bookingToDelete.eventType.slug } }, + }, + hideBranding: !!bookingToDelete.eventType.owner?.hideBranding, }); } diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index bed1992ed1..0e4742497d 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -1,4 +1,4 @@ -import type { PrismaClient, Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client"; +import type { Prisma, PrismaClient, Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client"; import { BookingStatus, WebhookTriggerEvents } from "@prisma/client"; import { scheduleTrigger } from "@calcom/app-store/zapier/lib/nodeScheduler"; @@ -10,6 +10,7 @@ import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import sendPayload from "@calcom/features/webhooks/lib/sendPayload"; import logger from "@calcom/lib/logger"; +import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar"; const log = logger.getChildLogger({ prefix: ["[handleConfirmation] book:user"] }); @@ -66,11 +67,24 @@ export async function handleConfirmation(args: { let updatedBookings: { scheduledJobs: string[]; id: number; + description: string | null; + location: string | null; + attendees: { + name: string; + email: string; + }[]; startTime: Date; endTime: Date; uid: string; smsReminderNumber: string | null; + metadata: Prisma.JsonValue | null; + customInputs: Prisma.JsonValue; eventType: { + bookingFields: Prisma.JsonValue | null; + slug: string; + owner: { + hideBranding?: boolean | null; + } | null; workflows: (WorkflowsOnEventTypes & { workflow: Workflow & { steps: WorkflowStep[]; @@ -104,6 +118,13 @@ export async function handleConfirmation(args: { select: { eventType: { select: { + slug: true, + bookingFields: true, + owner: { + select: { + hideBranding: true, + }, + }, workflows: { include: { workflow: { @@ -115,15 +136,21 @@ export async function handleConfirmation(args: { }, }, }, + description: true, + attendees: true, + location: true, uid: true, startTime: true, + metadata: true, endTime: true, smsReminderNumber: true, + customInputs: true, id: true, scheduledJobs: true, }, }) ); + const updatedBookingsResult = await Promise.all(updateBookingsPromise); updatedBookings = updatedBookings.concat(updatedBookingsResult); } else { @@ -142,6 +169,13 @@ export async function handleConfirmation(args: { select: { eventType: { select: { + slug: true, + bookingFields: true, + owner: { + select: { + hideBranding: true, + }, + }, workflows: { include: { workflow: { @@ -155,8 +189,13 @@ export async function handleConfirmation(args: { }, uid: true, startTime: true, + metadata: true, endTime: true, smsReminderNumber: true, + description: true, + attendees: true, + location: true, + customInputs: true, id: true, scheduledJobs: true, }, @@ -172,14 +211,21 @@ export async function handleConfirmation(args: { evtOfBooking.startTime = updatedBookings[index].startTime.toISOString(); evtOfBooking.endTime = updatedBookings[index].endTime.toISOString(); evtOfBooking.uid = updatedBookings[index].uid; + const eventTypeSlug = updatedBookings[index].eventType?.slug || ""; const isFirstBooking = index === 0; + const videoCallUrl = + bookingMetadataSchema.parse(updatedBookings[index].metadata || {})?.videoCallUrl || ""; await scheduleWorkflowReminders({ workflows: updatedBookings[index]?.eventType?.workflows || [], smsReminderNumber: updatedBookings[index].smsReminderNumber, - calendarEvent: evtOfBooking, + calendarEvent: { + ...evtOfBooking, + ...{ metadata: { videoCallUrl }, eventType: { slug: eventTypeSlug } }, + }, isFirstRecurringEvent: isFirstBooking, + hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding, }); } } diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 9d154152cd..f476a00cd5 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -198,6 +198,7 @@ const getEventTypesFromDB = async (eventTypeId: number) => { customInputs: true, disableGuests: true, users: userSelect, + slug: true, team: { select: { id: true, @@ -227,6 +228,11 @@ const getEventTypesFromDB = async (eventTypeId: number) => { seatsShowAttendees: true, bookingLimits: true, durationLimits: true, + owner: { + select: { + hideBranding: true, + }, + }, workflows: { include: { workflow: { @@ -800,6 +806,7 @@ async function handler( id: organizerUser.id, name: organizerUser.name || "Nameless", email: organizerUser.email || "Email-less", + username: organizerUser.username || undefined, timeZone: organizerUser.timeZone, language: { translate: tOrganizer, locale: organizerUser.locale ?? "en" }, timeFormat: organizerUser.timeFormat === 24 ? TimeFormat.TWENTY_FOUR_HOUR : TimeFormat.TWELVE_HOUR, @@ -1441,7 +1448,7 @@ async function handler( await scheduleWorkflowReminders({ workflows: eventType.workflows, smsReminderNumber: smsReminderNumber || null, - calendarEvent: { ...evt, responses, ...{ metadata } }, + calendarEvent: { ...evt, ...{ metadata, eventType: { slug: eventType.slug } } }, requiresConfirmation: evt.requiresConfirmation || false, isRescheduleEvent: !!rescheduleUid, isFirstRecurringEvent: true, @@ -2058,10 +2065,14 @@ async function handler( await scheduleWorkflowReminders({ workflows: eventType.workflows, smsReminderNumber: smsReminderNumber || null, - calendarEvent: { ...evt, responses, ...{ metadata: metadataFromEvent } }, + calendarEvent: { + ...evt, + ...{ metadata: metadataFromEvent, eventType: { slug: eventType.slug } }, + }, requiresConfirmation: evt.requiresConfirmation || false, isRescheduleEvent: !!rescheduleUid, isFirstRecurringEvent: true, + hideBranding: !!eventType.owner?.hideBranding, }); } catch (error) { log.error("Error while scheduling workflow reminders", error); diff --git a/packages/features/ee/workflows/api/scheduleEmailReminders.ts b/packages/features/ee/workflows/api/scheduleEmailReminders.ts index ffe0f7a61d..c0b26a54c7 100644 --- a/packages/features/ee/workflows/api/scheduleEmailReminders.ts +++ b/packages/features/ee/workflows/api/scheduleEmailReminders.ts @@ -5,6 +5,7 @@ import sgMail from "@sendgrid/mail"; import type { NextApiRequest, NextApiResponse } from "next"; import dayjs from "@calcom/dayjs"; +import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { defaultHandler } from "@calcom/lib/server"; import prisma from "@calcom/prisma"; import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; @@ -164,51 +165,63 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { let emailContent = { emailSubject: reminder.workflowStep.emailSubject || "", - emailBody: { - text: reminder.workflowStep.reminderBody || "", - html: `
${reminder.workflowStep.reminderBody || ""}`, - }, + emailBody: `${reminder.workflowStep.reminderBody || ""}`, }; - switch (reminder.workflowStep.template) { - case WorkflowTemplates.REMINDER: - emailContent = emailReminderTemplate( - reminder.booking.startTime.toISOString() || "", - reminder.booking.endTime.toISOString() || "", - reminder.booking.eventType?.title || "", - timeZone || "", - attendeeName || "", - name || "" - ); - break; - case WorkflowTemplates.CUSTOM: - const variables: VariablesType = { - eventName: reminder.booking?.eventType?.title || "", - organizerName: reminder.booking.user?.name || "", - attendeeName: reminder.booking.attendees[0].name, - attendeeEmail: reminder.booking.attendees[0].email, - eventDate: dayjs(reminder.booking.startTime).tz(timeZone), - eventTime: dayjs(reminder.booking.startTime).tz(timeZone), - timeZone: timeZone, - location: reminder.booking.location || "", - additionalNotes: reminder.booking.description, - responses: reminder.booking.responses, - meetingUrl: bookingMetadataSchema.parse(reminder.booking.metadata || {})?.videoCallUrl, - }; - const emailSubject = await customTemplate( - reminder.workflowStep.emailSubject || "", - variables, - locale || "" - ); - emailContent.emailSubject = emailSubject.text; - emailContent.emailBody = await customTemplate( - reminder.workflowStep.reminderBody || "", - variables, - locale || "" - ); - break; + let emailBodyEmpty = false; + + if (reminder.workflowStep.reminderBody) { + const { responses } = getCalEventResponses({ + bookingFields: reminder.booking.eventType?.bookingFields ?? null, + booking: reminder.booking, + }); + + const variables: VariablesType = { + eventName: reminder.booking.eventType?.title || "", + organizerName: reminder.booking.user?.name || "", + attendeeName: reminder.booking.attendees[0].name, + attendeeEmail: reminder.booking.attendees[0].email, + eventDate: dayjs(reminder.booking.startTime).tz(timeZone), + eventEndTime: dayjs(reminder.booking?.endTime).tz(timeZone), + timeZone: timeZone, + location: reminder.booking.location || "", + additionalNotes: reminder.booking.description, + responses: responses, + meetingUrl: bookingMetadataSchema.parse(reminder.booking.metadata || {})?.videoCallUrl, + cancelLink: `/booking/${reminder.booking.uid}?cancel=true`, + rescheduleLink: `/${reminder.booking.user?.username}/${reminder.booking.eventType?.slug}?rescheduleUid=${reminder.booking.uid}`, + }; + const emailSubject = customTemplate( + reminder.workflowStep.emailSubject || "", + variables, + locale || "", + !!reminder.booking.user?.hideBranding + ).text; + emailContent.emailSubject = emailSubject; + emailContent.emailBody = customTemplate( + reminder.workflowStep.reminderBody || "", + variables, + locale || "", + !!reminder.booking.user?.hideBranding + ).html; + + emailBodyEmpty = + customTemplate(reminder.workflowStep.reminderBody || "", variables, locale || "").text.length === 0; + } else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) { + emailContent = emailReminderTemplate( + false, + reminder.workflowStep.action, + reminder.booking.startTime.toISOString() || "", + reminder.booking.endTime.toISOString() || "", + reminder.booking.eventType?.title || "", + timeZone || "", + attendeeName || "", + name || "", + !!reminder.booking.user?.hideBranding + ); } - if (emailContent.emailSubject.length > 0 && emailContent.emailBody.text.length > 0 && sendTo) { + + if (emailContent.emailSubject.length > 0 && !emailBodyEmpty && sendTo) { const batchIdResponse = await client.request({ url: "/v3/mail/batch", method: "POST", @@ -224,8 +237,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { name: reminder.workflowStep.sender || "Cal.com", }, subject: emailContent.emailSubject, - text: emailContent.emailBody.text, - html: emailContent.emailBody.html, + html: emailContent.emailBody, batchId: batchId, sendAt: dayjs(reminder.scheduledDate).unix(), replyTo: reminder.booking.user?.email || senderEmail, diff --git a/packages/features/ee/workflows/api/scheduleSMSReminders.ts b/packages/features/ee/workflows/api/scheduleSMSReminders.ts index 3d1d0a5b07..46f6d0e1f9 100644 --- a/packages/features/ee/workflows/api/scheduleSMSReminders.ts +++ b/packages/features/ee/workflows/api/scheduleSMSReminders.ts @@ -3,6 +3,7 @@ import { WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@prisma/cli import type { NextApiRequest, NextApiResponse } from "next"; import dayjs from "@calcom/dayjs"; +import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { defaultHandler } from "@calcom/lib/server"; import prisma from "@calcom/prisma"; import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; @@ -87,38 +88,46 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { : reminder.booking?.user?.locale; let message: string | null = reminder.workflowStep.reminderBody; - switch (reminder.workflowStep.template) { - case WorkflowTemplates.REMINDER: - message = smsReminderTemplate( - reminder.booking?.startTime.toISOString() || "", - reminder.booking?.eventType?.title || "", - timeZone || "", - attendeeName || "", - userName - ); - break; - case WorkflowTemplates.CUSTOM: - const variables: VariablesType = { - eventName: reminder.booking?.eventType?.title, - organizerName: reminder.booking?.user?.name || "", - attendeeName: reminder.booking?.attendees[0].name, - attendeeEmail: reminder.booking?.attendees[0].email, - eventDate: dayjs(reminder.booking?.startTime).tz(timeZone), - eventTime: dayjs(reminder.booking?.startTime).tz(timeZone), - timeZone: timeZone, - location: reminder.booking?.location || "", - additionalNotes: reminder.booking?.description, - responses: reminder.booking?.responses, - meetingUrl: bookingMetadataSchema.parse(reminder.booking?.metadata || {})?.videoCallUrl, - }; - const customMessage = await customTemplate( - reminder.workflowStep.reminderBody || "", - variables, - locale || "" - ); - message = customMessage.text; - break; + + if (reminder.workflowStep.reminderBody) { + const { responses } = getCalEventResponses({ + bookingFields: reminder.booking.eventType?.bookingFields ?? null, + booking: reminder.booking, + }); + + const variables: VariablesType = { + eventName: reminder.booking?.eventType?.title, + organizerName: reminder.booking?.user?.name || "", + attendeeName: reminder.booking?.attendees[0].name, + attendeeEmail: reminder.booking?.attendees[0].email, + eventDate: dayjs(reminder.booking?.startTime).tz(timeZone), + eventEndTime: dayjs(reminder.booking?.endTime).tz(timeZone), + timeZone: timeZone, + location: reminder.booking?.location || "", + additionalNotes: reminder.booking?.description, + responses: responses, + meetingUrl: bookingMetadataSchema.parse(reminder.booking?.metadata || {})?.videoCallUrl, + cancelLink: `/booking/${reminder.booking.uid}?cancel=true`, + rescheduleLink: `/${reminder.booking.user?.username}/${reminder.booking.eventType?.slug}?rescheduleUid=${reminder.booking.uid}`, + }; + const customMessage = customTemplate( + reminder.workflowStep.reminderBody || "", + variables, + locale || "" + ); + message = customMessage.text; + } else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) { + message = smsReminderTemplate( + false, + reminder.workflowStep.action, + reminder.booking?.startTime.toISOString() || "", + reminder.booking?.eventType?.title || "", + timeZone || "", + attendeeName || "", + userName + ); } + if (message?.length && message?.length > 0 && sendTo) { const scheduledSMS = await twilio.scheduleSMS(sendTo, message, reminder.scheduledDate, senderID); diff --git a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx index 284c087b7e..236cb77be8 100644 --- a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx +++ b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx @@ -13,7 +13,7 @@ import type { MultiSelectCheckboxesOptionType as Option } from "@calcom/ui"; import { Button, Label, MultiSelectCheckboxes, TextField } from "@calcom/ui"; import { ArrowDown, Trash2 } from "@calcom/ui/components/icon"; -import { isSMSAction } from "../lib/isSMSAction"; +import { isSMSAction } from "../lib/actionHelperFunctions"; import type { FormValues } from "../pages/workflow"; import { AddActionDialog } from "./AddActionDialog"; import { DeleteDialog } from "./DeleteDialog"; diff --git a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx index 8ad1b8472d..9e1497046c 100644 --- a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx @@ -35,11 +35,13 @@ import { AddVariablesDropdown, Input, } from "@calcom/ui"; -import { ArrowDown, MoreHorizontal, Trash2, HelpCircle } from "@calcom/ui/components/icon"; +import { ArrowDown, MoreHorizontal, Trash2, HelpCircle, Info } from "@calcom/ui/components/icon"; +import { isAttendeeAction, isSMSAction } from "../lib/actionHelperFunctions"; import { DYNAMIC_TEXT_VARIABLES } from "../lib/constants"; import { getWorkflowTemplateOptions, getWorkflowTriggerOptions } from "../lib/getOptions"; -import { isSMSAction } from "../lib/isSMSAction"; +import emailReminderTemplate from "../lib/reminders/templates/emailReminderTemplate"; +import smsReminderTemplate from "../lib/reminders/templates/smsReminderTemplate"; import type { FormValues } from "../pages/workflow"; import { TimeTimeUnitInput } from "./TimeTimeUnitInput"; @@ -69,12 +71,14 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { step?.action === WorkflowActions.SMS_NUMBER ? true : false ); + const [updateTemplate, setUpdateTemplate] = useState(false); + const [firstRender, setFirstRender] = useState(true); + const [isSenderIdNeeded, setIsSenderIdNeeded] = useState( step?.action === WorkflowActions.SMS_NUMBER || step?.action === WorkflowActions.SMS_ATTENDEE ? true : false ); - useEffect(() => { setNumberVerified( !!step && @@ -86,10 +90,6 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { step?.action === WorkflowActions.EMAIL_ADDRESS ? true : false ); - const [isCustomReminderBodyNeeded, setIsCustomReminderBodyNeeded] = useState( - step?.template === WorkflowTemplates.CUSTOM ? true : false - ); - const [isEmailSubjectNeeded, setIsEmailSubjectNeeded] = useState( step?.action === WorkflowActions.EMAIL_ATTENDEE || step?.action === WorkflowActions.EMAIL_HOST || @@ -110,6 +110,28 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { const triggerOptions = getWorkflowTriggerOptions(t); const templateOptions = getWorkflowTemplateOptions(t); + if (step && form.getValues(`steps.${step.stepNumber - 1}.template`) === WorkflowTemplates.REMINDER) { + if (!form.getValues(`steps.${step.stepNumber - 1}.reminderBody`)) { + if (isSMSAction(form.getValues(`steps.${step.stepNumber - 1}.action`))) { + const smsBody = smsReminderTemplate(true, form.getValues(`steps.${step.stepNumber - 1}.action`)); + form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, smsBody); + } else { + const reminderBodyTemplate = emailReminderTemplate( + true, + form.getValues(`steps.${step.stepNumber - 1}.action`) + ).emailBody; + form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, reminderBodyTemplate); + } + } + if (!form.getValues(`steps.${step.stepNumber - 1}.emailSubject`)) { + const subjectTemplate = emailReminderTemplate( + true, + form.getValues(`steps.${step.stepNumber - 1}.action`) + ).emailSubject; + form.setValue(`steps.${step.stepNumber - 1}.emailSubject`, subjectTemplate); + } + } + const { ref: emailSubjectFormRef, ...restEmailSubjectForm } = step ? form.register(`steps.${step.stepNumber - 1}.emailSubject`) : { ref: null, name: "" }; @@ -259,6 +281,10 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {{t("testing_workflow_info_message")}
+