diff --git a/apps/web/app/(use-page-wrapper)/connect-and-join/page.tsx b/apps/web/app/(use-page-wrapper)/connect-and-join/page.tsx index a271ffc7ec3f8c..1666fa6c1e470f 100644 --- a/apps/web/app/(use-page-wrapper)/connect-and-join/page.tsx +++ b/apps/web/app/(use-page-wrapper)/connect-and-join/page.tsx @@ -1,5 +1,7 @@ import { _generateMetadata } from "app/_utils"; +import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired"; + import LegacyPage from "~/connect-and-join/connect-and-join-view"; export const generateMetadata = async () => { @@ -9,4 +11,11 @@ export const generateMetadata = async () => { ); }; -export default LegacyPage; +const ServerPage = async () => { + return ( + + + + ); +}; +export default ServerPage; diff --git a/apps/web/modules/connect-and-join/connect-and-join-view.tsx b/apps/web/modules/connect-and-join/connect-and-join-view.tsx index 044a4d0faa1ec0..66645a6ae3a323 100644 --- a/apps/web/modules/connect-and-join/connect-and-join-view.tsx +++ b/apps/web/modules/connect-and-join/connect-and-join-view.tsx @@ -85,6 +85,4 @@ function ConnectAndJoin() { ); } -ConnectAndJoin.requiresLicense = true; - export default ConnectAndJoin; diff --git a/help/availabilities/set-up-your-availability.mdx b/help/availabilities/set-up-your-availability.mdx new file mode 100644 index 00000000000000..4fa2410600e6fe --- /dev/null +++ b/help/availabilities/set-up-your-availability.mdx @@ -0,0 +1,45 @@ +--- +title: 'Set up your availability' +--- + +Follow these step-by-step instructions to configure your availability in Cal.com based on your individual preferences: + + + + * Log in to your Cal.com account. + * Click Availability in the sidebar. You’ll see your existing availability schedules, such as "Working Hours" or any other you’ve added before. + + + * Click New in the top-right corner. +* Enter a name for your schedule (e.g., "Test Availability"). +* Click Continue to create your schedule. +By default, the schedule is set to 9:00 AM to 5:00 PM. + + + * Enable/Disable Days: +Toggle days on or off based on when you're available. +For example, you can set availability until 1:00 PM on a Saturday. +* Edit Times for Specific Days: +Click a time box to adjust availability for specific days. +For example, change Monday’s availability to 9:00 AM to 6:00 PM. +* Copy Times to Other Days: +After setting times for one day, click Copy Times To and select other days (e.g., Tuesday–Friday). +* Add Multiple Time Slots: +Add additional time slots to split your availability (e.g., 9:00 AM–11:00 AM and 5:00 PM–7:30 PM). + + + Use day overrides for special cases when your availability changes for specific dates. +* Click Add an Override. +Example: Choose a date (e.g., Wednesday) and mark yourself as completely unavailable. +Overrides are automatically archived once the date has passed. + + + Set a specific time zone for your availability. +Useful if your availability changes when traveling to another country. + + + If you notice times not showing up as expected, click Launch Troubleshooter. +This tool helps identify issues based on how Cal.com determines your availability. + + + diff --git a/packages/app-store/apps.metadata.generated.ts b/packages/app-store/apps.metadata.generated.ts index fd6b0db1a2e740..9d9112a4657689 100644 --- a/packages/app-store/apps.metadata.generated.ts +++ b/packages/app-store/apps.metadata.generated.ts @@ -51,8 +51,10 @@ import linear_config_json from "./linear/config.json"; import make_config_json from "./make/config.json"; import matomo_config_json from "./matomo/config.json"; import metapixel_config_json from "./metapixel/config.json"; +import millis_ai_config_json from "./millis-ai/config.json"; import mirotalk_config_json from "./mirotalk/config.json"; import mock_payment_app_config_json from "./mock-payment-app/config.json"; +import monobot_config_json from "./monobot/config.json"; import n8n_config_json from "./n8n/config.json"; import nextcloudtalk_config_json from "./nextcloudtalk/config.json"; import { metadata as office365calendar__metadata_ts } from "./office365calendar/_metadata"; @@ -155,8 +157,10 @@ export const appStoreMetadata = { make: make_config_json, matomo: matomo_config_json, metapixel: metapixel_config_json, + "millis-ai": millis_ai_config_json, mirotalk: mirotalk_config_json, "mock-payment-app": mock_payment_app_config_json, + monobot: monobot_config_json, n8n: n8n_config_json, nextcloudtalk: nextcloudtalk_config_json, office365calendar: office365calendar__metadata_ts, diff --git a/packages/app-store/apps.server.generated.ts b/packages/app-store/apps.server.generated.ts index 484885c4a5b6a5..00762330441720 100644 --- a/packages/app-store/apps.server.generated.ts +++ b/packages/app-store/apps.server.generated.ts @@ -51,8 +51,10 @@ export const apiHandlers = { make: import("./make/api"), matomo: import("./matomo/api"), metapixel: import("./metapixel/api"), + "millis-ai": import("./millis-ai/api"), mirotalk: import("./mirotalk/api"), "mock-payment-app": import("./mock-payment-app/api"), + monobot: import("./monobot/api"), n8n: import("./n8n/api"), nextcloudtalk: import("./nextcloudtalk/api"), office365calendar: import("./office365calendar/api"), diff --git a/packages/app-store/millis-ai/DESCRIPTION.md b/packages/app-store/millis-ai/DESCRIPTION.md new file mode 100644 index 00000000000000..6f4b021ed5327f --- /dev/null +++ b/packages/app-store/millis-ai/DESCRIPTION.md @@ -0,0 +1,15 @@ +--- +items: + - 0.jpg + - 1.jpg + - 2.jpg + - 3.jpg + - 4.jpg +--- + +{DESCRIPTION} + +Effortlessly create advanced LLM-based voice applications with ultra-low latency — The Fastest on the Market. + +Millis AI gives you everything you need to create instant, human-like, and affordable voice agents. + diff --git a/packages/app-store/millis-ai/api/add.ts b/packages/app-store/millis-ai/api/add.ts new file mode 100644 index 00000000000000..12c2d6dc006a54 --- /dev/null +++ b/packages/app-store/millis-ai/api/add.ts @@ -0,0 +1,20 @@ +import { createDefaultInstallation } from "@calcom/app-store/_utils/installation"; +import type { AppDeclarativeHandler } from "@calcom/types/AppHandler"; + +import appConfig from "../config.json"; + +const handler: AppDeclarativeHandler = { + appType: appConfig.type, + variant: appConfig.variant, + slug: appConfig.slug, + supportsMultipleInstalls: false, + handlerType: "add", + redirect: { + newTab: true, + url: "https://www.millis.ai/integrations/cal-com", + }, + createCredential: ({ appType, user, slug, teamId }) => + createDefaultInstallation({ appType, user: user, slug, key: {}, teamId }), +}; + +export default handler; diff --git a/packages/app-store/millis-ai/api/index.ts b/packages/app-store/millis-ai/api/index.ts new file mode 100644 index 00000000000000..4c0d2ead01e1f9 --- /dev/null +++ b/packages/app-store/millis-ai/api/index.ts @@ -0,0 +1 @@ +export { default as add } from "./add"; diff --git a/packages/app-store/millis-ai/components/.gitkeep b/packages/app-store/millis-ai/components/.gitkeep new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/packages/app-store/millis-ai/config.json b/packages/app-store/millis-ai/config.json new file mode 100644 index 00000000000000..0a1e40c0a18ffd --- /dev/null +++ b/packages/app-store/millis-ai/config.json @@ -0,0 +1,16 @@ +{ + "/*": "Don't modify slug - If required, do it using cli edit command", + "name": "Millis AI", + "slug": "millis-ai", + "type": "millis-ai_automation", + "logo": "icon.png", + "url": "https://docs.millis.ai/tutorials/create-voice-agent-for-appointment-scheduling", + "variant": "automation", + "categories": ["automation"], + "publisher": "Millis AI", + "email": "thach@millis.ai", + "description": "Build next-gen voice agents with 500ms latency\r", + "isTemplate": false, + "__createdUsingCli": true, + "__template": "link-as-an-app" +} diff --git a/packages/app-store/millis-ai/index.ts b/packages/app-store/millis-ai/index.ts new file mode 100644 index 00000000000000..d7f36022040096 --- /dev/null +++ b/packages/app-store/millis-ai/index.ts @@ -0,0 +1 @@ +export * as api from "./api"; diff --git a/packages/app-store/millis-ai/package.json b/packages/app-store/millis-ai/package.json new file mode 100644 index 00000000000000..af87a74a6a2eab --- /dev/null +++ b/packages/app-store/millis-ai/package.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "private": true, + "name": "@calcom/millis-ai", + "version": "0.0.0", + "main": "./index.ts", + "dependencies": { + "@calcom/lib": "*" + }, + "devDependencies": { + "@calcom/types": "*" + }, + "description": "Build next-gen voice agents with 500ms latency\r" +} diff --git a/packages/app-store/millis-ai/static/0.jpg b/packages/app-store/millis-ai/static/0.jpg new file mode 100644 index 00000000000000..5338e1205cac6b Binary files /dev/null and b/packages/app-store/millis-ai/static/0.jpg differ diff --git a/packages/app-store/millis-ai/static/1.jpg b/packages/app-store/millis-ai/static/1.jpg new file mode 100644 index 00000000000000..2abd7f191b7e2a Binary files /dev/null and b/packages/app-store/millis-ai/static/1.jpg differ diff --git a/packages/app-store/millis-ai/static/2.jpg b/packages/app-store/millis-ai/static/2.jpg new file mode 100644 index 00000000000000..f5f32e036c6b3a Binary files /dev/null and b/packages/app-store/millis-ai/static/2.jpg differ diff --git a/packages/app-store/millis-ai/static/3.jpg b/packages/app-store/millis-ai/static/3.jpg new file mode 100644 index 00000000000000..0e0b50c17b321b Binary files /dev/null and b/packages/app-store/millis-ai/static/3.jpg differ diff --git a/packages/app-store/millis-ai/static/4.jpg b/packages/app-store/millis-ai/static/4.jpg new file mode 100644 index 00000000000000..9e7402937f47dc Binary files /dev/null and b/packages/app-store/millis-ai/static/4.jpg differ diff --git a/packages/app-store/millis-ai/static/icon.png b/packages/app-store/millis-ai/static/icon.png new file mode 100644 index 00000000000000..0d8d5676fb62fe Binary files /dev/null and b/packages/app-store/millis-ai/static/icon.png differ diff --git a/packages/app-store/monobot/DESCRIPTION.md b/packages/app-store/monobot/DESCRIPTION.md new file mode 100644 index 00000000000000..0ada74cb637f6f --- /dev/null +++ b/packages/app-store/monobot/DESCRIPTION.md @@ -0,0 +1,20 @@ +--- +items: + - iframe: { src: https://www.youtube.com/watch?v=Jm2elbC9UmI } + - 1.jpeg + - 2.jpeg + - 3.jpeg +--- + +{DESCRIPTION} + +Monobot allows you to setup versatile AI Virtual Assistants for chat and voice in minutes. + +### Easy-to-Use Intuitive Configuration UI +Dozens of industries are already covered by our comprehensive Virtual Assistant template library to give you a head start. + +### Human-like Voice Experience in Many Languages +Our bots sound just like humans, can speak over 20 languages plus you can choose from many voices! + +### Integrations +Integrations with calendars, messengers, external APIs and much more is already included at no extra cost. diff --git a/packages/app-store/monobot/api/add.ts b/packages/app-store/monobot/api/add.ts new file mode 100644 index 00000000000000..637334d0bcf98a --- /dev/null +++ b/packages/app-store/monobot/api/add.ts @@ -0,0 +1,20 @@ +import { createDefaultInstallation } from "@calcom/app-store/_utils/installation"; +import type { AppDeclarativeHandler } from "@calcom/types/AppHandler"; + +import appConfig from "../config.json"; + +const handler: AppDeclarativeHandler = { + appType: appConfig.type, + variant: appConfig.variant, + slug: appConfig.slug, + supportsMultipleInstalls: false, + handlerType: "add", + redirect: { + newTab: true, + url: "https://monobot.ai/?referralId=cal.com", + }, + createCredential: ({ appType, user, slug, teamId }) => + createDefaultInstallation({ appType, user: user, slug, key: {}, teamId }), +}; + +export default handler; diff --git a/packages/app-store/monobot/api/index.ts b/packages/app-store/monobot/api/index.ts new file mode 100644 index 00000000000000..4c0d2ead01e1f9 --- /dev/null +++ b/packages/app-store/monobot/api/index.ts @@ -0,0 +1 @@ +export { default as add } from "./add"; diff --git a/packages/app-store/monobot/components/.gitkeep b/packages/app-store/monobot/components/.gitkeep new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/packages/app-store/monobot/config.json b/packages/app-store/monobot/config.json new file mode 100644 index 00000000000000..99df108be74484 --- /dev/null +++ b/packages/app-store/monobot/config.json @@ -0,0 +1,16 @@ +{ + "/*": "Don't modify slug - If required, do it using cli edit command", + "name": "Monobot CX", + "slug": "monobot", + "type": "monobot_automation", + "logo": "icon.svg", + "url": "https://monobot.ai/?referralId=cal.com", + "variant": "automation", + "categories": ["automation"], + "publisher": "Monobot CX", + "email": "contact@monobot.ai", + "description": "Crafting your personalized AI-driven assistant is easy and fast.", + "isTemplate": false, + "__createdUsingCli": true, + "__template": "link-as-an-app" +} diff --git a/packages/app-store/monobot/index.ts b/packages/app-store/monobot/index.ts new file mode 100644 index 00000000000000..d7f36022040096 --- /dev/null +++ b/packages/app-store/monobot/index.ts @@ -0,0 +1 @@ +export * as api from "./api"; diff --git a/packages/app-store/monobot/package.json b/packages/app-store/monobot/package.json new file mode 100644 index 00000000000000..ca31e9546219e6 --- /dev/null +++ b/packages/app-store/monobot/package.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "private": true, + "name": "@calcom/monobot", + "version": "0.0.0", + "main": "./index.ts", + "dependencies": { + "@calcom/lib": "*" + }, + "devDependencies": { + "@calcom/types": "*" + }, + "description": "Crafting your personalized AI-driven assistant is easy and fast." +} diff --git a/packages/app-store/monobot/static/1.jpeg b/packages/app-store/monobot/static/1.jpeg new file mode 100644 index 00000000000000..1449f38ac6687c Binary files /dev/null and b/packages/app-store/monobot/static/1.jpeg differ diff --git a/packages/app-store/monobot/static/2.jpeg b/packages/app-store/monobot/static/2.jpeg new file mode 100644 index 00000000000000..26625d2dc4d1e6 Binary files /dev/null and b/packages/app-store/monobot/static/2.jpeg differ diff --git a/packages/app-store/monobot/static/3.jpeg b/packages/app-store/monobot/static/3.jpeg new file mode 100644 index 00000000000000..c03df45e738109 Binary files /dev/null and b/packages/app-store/monobot/static/3.jpeg differ diff --git a/packages/app-store/monobot/static/icon.svg b/packages/app-store/monobot/static/icon.svg new file mode 100644 index 00000000000000..b31fff8645ce40 --- /dev/null +++ b/packages/app-store/monobot/static/icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/app-store/routing-forms/components/FormActions.tsx b/packages/app-store/routing-forms/components/FormActions.tsx index b1a9740824f6ac..1e04b9cef83bd4 100644 --- a/packages/app-store/routing-forms/components/FormActions.tsx +++ b/packages/app-store/routing-forms/components/FormActions.tsx @@ -1,9 +1,7 @@ -import type { App_RoutingForms_Form } from "@prisma/client"; import { useRouter } from "next/navigation"; import { createContext, forwardRef, useContext, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { v4 as uuidv4 } from "uuid"; -import { z } from "zod"; import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; import { RoutingFormEmbedButton, RoutingFormEmbedDialog } from "@calcom/features/embed/RoutingFormEmbed"; @@ -34,15 +32,24 @@ import { } from "@calcom/ui"; import getFieldIdentifier from "../lib/getFieldIdentifier"; -import type { SerializableForm } from "../types/types"; -type RoutingForm = SerializableForm; +type FormField = { + identifier?: string; + id: string; + type: string; + label: string; + routerId?: string | null; +}; + +type RoutingForm = { + id: string; + name: string; + disabled: boolean; + fields?: FormField[]; +}; + export type NewFormDialogState = { action: "new" | "duplicate"; target: string | null } | null; export type SetNewFormDialogState = React.Dispatch>; -const newFormModalQuerySchema = z.object({ - action: z.literal("new").or(z.literal("duplicate")), - target: z.string().optional(), -}); function NewFormDialog({ appUrl, diff --git a/packages/app-store/routing-forms/pages/forms/[...appPages].tsx b/packages/app-store/routing-forms/pages/forms/[...appPages].tsx index 58b69ef0683293..4ed6c57c23f868 100644 --- a/packages/app-store/routing-forms/pages/forms/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/forms/[...appPages].tsx @@ -201,7 +201,10 @@ export default function RoutingForms({ SkeletonLoader={SkeletonLoaderTeamList}>
- {forms?.map(({ form, readOnly }, index) => { + {forms?.map(({ form, readOnly, hasError }, index) => { + // Make the form read only if it has an error + // TODO: Consider showing error in UI so user can report and get it fixed. + readOnly = readOnly || hasError; if (!form) { return null; } diff --git a/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx b/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx index 8827a57ea2247d..0f84edc0563af1 100644 --- a/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx @@ -8,11 +8,11 @@ import { Query, Builder, Utils as QbUtils } from "react-awesome-query-builder"; import type { ImmutableTree, BuilderProps, Config } from "react-awesome-query-builder"; import type { JsonTree } from "react-awesome-query-builder"; import type { UseFormReturn } from "react-hook-form"; +import type { z } from "zod"; import { areTheySiblingEntitites } from "@calcom/lib/entityPermissionUtils"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { buildEmptyQueryValue, raqbQueryValueUtils } from "@calcom/lib/raqb/raqbUtils"; -import type { App_RoutingForms_Form } from "@calcom/prisma/client"; import { SchedulingType } from "@calcom/prisma/client"; import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; @@ -49,7 +49,6 @@ import { isDynamicOperandField, } from "../../lib/getQueryBuilderConfig"; import isRouter from "../../lib/isRouter"; -import type { SerializableForm } from "../../types/types"; import type { GlobalRoute, LocalRoute, @@ -58,6 +57,7 @@ import type { EditFormRoute, AttributeRoutingConfig, } from "../../types/types"; +import type { zodRoutes } from "../../zod"; import { RouteActionType } from "../../zod"; type EventTypesByGroup = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]; @@ -912,7 +912,7 @@ const Routes = ({ const { data: allForms } = trpc.viewer.appRoutingForms.forms.useQuery(); - const notHaveAttributesQuery = ({ form }: { form: SerializableForm }) => { + const notHaveAttributesQuery = ({ form }: { form: { routes: z.infer } }) => { return form.routes?.every((route) => { if (isRouter(route)) { return true; diff --git a/packages/app-store/routing-forms/trpc/forms.handler.ts b/packages/app-store/routing-forms/trpc/forms.handler.ts index 907f3c5a8bce3a..e7b64882c4d763 100644 --- a/packages/app-store/routing-forms/trpc/forms.handler.ts +++ b/packages/app-store/routing-forms/trpc/forms.handler.ts @@ -1,12 +1,16 @@ +import type { z } from "zod"; + import { hasFilter } from "@calcom/features/filters/lib/hasFilter"; import { entityPrismaWhereClause, canEditEntity } from "@calcom/lib/entityPermissionUtils"; import logger from "@calcom/lib/logger"; +import { safeStringify } from "@calcom/lib/safeStringify"; import type { PrismaClient } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; import { entries } from "@calcom/prisma/zod-utils"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; import { getSerializableForm } from "../lib/getSerializableForm"; +import type { zodFields, zodRoutes } from "../zod"; import type { TFormSchema } from "./forms.schema"; interface FormsHandlerOptions { @@ -55,24 +59,54 @@ export const formsHandler = async ({ ctx, input }: FormsHandlerOptions) => { }), }); - const serializableForms = await Promise.all( - forms.map(async (form) => { - const [hasWriteAccess, serializedForm] = await Promise.all([ - canEditEntity(form, user.id), - getSerializableForm({ form }), - ]); - - return { - form: serializedForm, - readOnly: !hasWriteAccess, - }; - }) - ); - return { - filtered: serializableForms, + filtered: await buildFormsWithReadOnlyStatus(), totalCount: totalForms, }; + + async function buildFormsWithReadOnlyStatus() { + // Avoid crash of one form to crash entire listing + const settledFormsWithReadonlyStatus = await Promise.allSettled( + forms.map(async (form) => { + const [hasWriteAccess, serializedForm] = await Promise.all([ + canEditEntity(form, user.id), + getSerializableForm({ form }), + ]); + + return { + form: serializedForm, + readOnly: !hasWriteAccess, + }; + }) + ); + + const formsWithReadonlyStatus = settledFormsWithReadonlyStatus.map((result, index) => { + if (result.status === "fulfilled") { + // Normal case + return { + ...result.value, + hasError: false, + }; + } + + // Error case + const form = forms[index]; + log.error(`Error getting form ${form.id}: ${safeStringify(result.reason)}`); + + return { + form: { + ...form, + // Usually the error is in parsing routes/fields as they are JSON. So, we just set them empty, so that form can be still listed. + routes: [] as z.infer, + fields: [] as z.infer, + }, + // Consider it readonly as we don't know the status due to error + readOnly: true, + hasError: true, + }; + }); + return formsWithReadonlyStatus; + } }; export default formsHandler; diff --git a/packages/features/embed/Embed.tsx b/packages/features/embed/Embed.tsx index 1784fec9d6fc03..59fca8e601153d 100644 --- a/packages/features/embed/Embed.tsx +++ b/packages/features/embed/Embed.tsx @@ -2,7 +2,7 @@ import { Collapsible, CollapsibleContent } from "@radix-ui/react-collapsible"; import classNames from "classnames"; import { useSession } from "next-auth/react"; import { usePathname, useRouter } from "next/navigation"; -import type { RefObject } from "react"; +import type { RefObject, Dispatch, SetStateAction } from "react"; import { createRef, useRef, useState } from "react"; import type { ControlProps } from "react-select"; import { components } from "react-select"; @@ -233,12 +233,16 @@ const EmailEmbed = ({ username, orgSlug, isTeamEvent, + selectedDuration, + setSelectedDuration, userSettingsTimezone, }: { eventType?: EventType; username: string; orgSlug?: string; isTeamEvent: boolean; + selectedDuration: number | undefined; + setSelectedDuration: Dispatch>; userSettingsTimezone?: string; }) => { const { t, i18n } = useLocale(); @@ -274,7 +278,12 @@ const EmailEmbed = ({ shallow ); const event = useEvent(); - const schedule = useScheduleForEvent({ orgSlug, eventId: eventType?.id, isTeamEvent }); + const schedule = useScheduleForEvent({ + orgSlug, + eventId: eventType?.id, + isTeamEvent, + duration: selectedDuration, + }); const nonEmptyScheduleDays = useNonEmptyScheduleDays(schedule?.data?.slots); const onTimeSelect = (time: string) => { @@ -334,6 +343,15 @@ const EmailEmbed = ({ if (!eventType) { return null; } + if (!selectedDuration) { + setSelectedDuration(eventType.length); + } + + const multipleDurations = eventType?.metadata?.multipleDuration ?? []; + const durationsOptions = multipleDurations.map((duration) => ({ + label: `${duration} ${t("minutes")}`, + value: duration, + })); return (
@@ -388,12 +406,23 @@ const EmailEmbed = ({
{t("duration")}
- {t("minutes")}} - /> + {durationsOptions.length > 0 ? ( + + value={durationsOptions.find((option) => option.value === selectedDuration)} + options={durationsOptions} + onChange={(option) => { + setSelectedDuration(option?.value); + setSelectedDatesAndTimes({}); + }} + /> + ) : ( + {t("minutes")}} + /> + )}
@@ -416,6 +445,7 @@ const EmailEmbedPreview = ({ month, selectedDateAndTime, calLink, + selectedDuration, userSettingsTimezone, }: { eventType: EventType; @@ -425,6 +455,7 @@ const EmailEmbedPreview = ({ month?: string; selectedDateAndTime: { [key: string]: string[] }; calLink: string; + selectedDuration: number | undefined; userSettingsTimezone?: string; }) => { const { t } = useLocale(); @@ -472,7 +503,7 @@ const EmailEmbedPreview = ({ lineHeight: "17px", color: "#333333", }}> - {t("duration")}: {eventType.length} mins + {t("duration")}: {selectedDuration} mins
@@ -534,9 +565,9 @@ const EmailEmbedPreview = ({ // So we add 'team/' to the url. const bookingURL = `${eventType.bookerUrl}/${ eventType.teamId !== null ? "team/" : "" - }${username}/${eventType.slug}?duration=${ - eventType.length - }&date=${key}&month=${month}&slot=${time}&cal.tz=${timezone}`; + }${username}/${ + eventType.slug + }?duration=${selectedDuration}&date=${key}&month=${month}&slot=${time}&cal.tz=${timezone}`; return ( embed.type === embedType); + const [selectedDuration, setSelectedDuration] = useState(eventTypeData?.eventType.length); const [isEmbedCustomizationOpen, setIsEmbedCustomizationOpen] = useState(true); const [isBookingCustomizationOpen, setIsBookingCustomizationOpen] = useState(true); @@ -895,6 +927,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({ userSettingsTimezone={userSettings?.timeZone} orgSlug={data?.user?.org?.slug} isTeamEvent={!!teamSlug} + selectedDuration={selectedDuration} + setSelectedDuration={setSelectedDuration} /> ) : (
@@ -1238,6 +1272,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
- {!isDisabled && ( - - - - )} +
+ {!isDisabled && ( + + + + )} + {t("within_date_range")} +
- {t("within_date_range")} 
 {t("into_the_future")}
-
+
- + {user.name || "Nameless User"}