From 1025238eee9a40668455eac922bc56c1acfe69f8 Mon Sep 17 00:00:00 2001 From: Peer Richelsen Date: Thu, 28 Mar 2024 13:59:01 +0000 Subject: [PATCH] feat: cal.ai enterprise phone calls (#14100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added empty layout * init * yarn lock due to package changes * added retell ai to .env * removed ton of glue code * nit * Discard changes to package.json * Upgrades lucide-react * minor UI fixes * nit * nit * feat: save progress * feat: v1 * fix: type error * feat: change schema * fix: type error and testr * chore: update agent * chore: change default prompt * feat: feedback and improvements * fix: type error * fix: type error * hidden for now while in trial * added i18n and removed some comments * feat: add cal api key * fix: type error --------- Co-authored-by: Omar López Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Udit Takkar Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> --- .env.example | 7 +- .../components/apps/InstallAppButtonChild.tsx | 2 +- .../eventtype/AIEventController.tsx | 279 ++++++++++++++++++ apps/web/components/eventtype/EventAITab.tsx | 10 + .../eventtype/EventTypeSingleLayout.tsx | 10 + apps/web/components/ui/LinkIconButton.tsx | 4 +- .../views/event-types-single-view.tsx | 17 ++ apps/web/public/static/locales/en/common.json | 9 + package.json | 2 +- .../giphy/components/SearchDialog.tsx | 3 +- .../components/event-meta/Details.tsx | 3 +- .../components/event-meta/getPayIcon.ts | 4 +- .../components/event-meta/getPriceIcon.ts | 3 +- .../ee/workflows/components/EmptyScreen.tsx | 3 +- .../features/eventtypes/lib/getPublicEvent.ts | 2 + packages/features/eventtypes/lib/types.ts | 8 + .../features/insights/filters/FilterType.tsx | 3 +- packages/features/shell/Shell.tsx | 21 +- packages/lib/event-types/getEventTypeById.ts | 1 + packages/lib/server/eventTypeSelect.ts | 1 + packages/platform/atoms/package.json | 2 +- .../sdk/src/endpoints/events/types.ts | 9 + .../migration.sql | 23 ++ packages/prisma/schema.prisma | 59 ++-- packages/prisma/zod-utils.ts | 18 ++ .../server/routers/viewer/eventTypes/types.ts | 11 + .../viewer/eventTypes/update.handler.ts | 30 ++ .../routers/viewer/organizations/_router.tsx | 8 + .../organizations/createPhoneCall.handler.ts | 180 +++++++++++ packages/ui/components/alert/Alert.tsx | 3 +- packages/ui/components/apps/AppCard.tsx | 2 +- packages/ui/components/badge/Badge.tsx | 3 +- packages/ui/components/button/Button.tsx | 5 +- .../ui/components/button/LinkIconButton.tsx | 3 +- .../components/createButton/CreateButton.tsx | 2 +- packages/ui/components/dialog/Dialog.tsx | 3 +- .../ui/components/form/dropdown/Dropdown.tsx | 5 +- .../navigation/tabs/HorizontalTabItem.tsx | 3 +- .../navigation/tabs/VerticalTabItem.tsx | 3 +- packages/ui/package.json | 2 +- turbo.json | 1 + yarn.lock | 16 +- 42 files changed, 724 insertions(+), 59 deletions(-) create mode 100644 apps/web/components/eventtype/AIEventController.tsx create mode 100644 apps/web/components/eventtype/EventAITab.tsx create mode 100644 packages/prisma/migrations/20240328080307_add_ai_phone_call_config/migration.sql create mode 100644 packages/trpc/server/routers/viewer/organizations/createPhoneCall.handler.ts diff --git a/.env.example b/.env.example index 1a0c5dc675..dd14199244 100644 --- a/.env.example +++ b/.env.example @@ -345,4 +345,9 @@ SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN=1 NEXT_PUBLIC_API_V2_URL="http://localhost:5555/api/v2" # Ratelimiting via unkey -UNKEY_ROOT_KEY= \ No newline at end of file +UNKEY_ROOT_KEY= + + +# Used for Cal.ai Enterprise Voice AI Agents +# https://retellai.com +RETELL_AI_KEY= diff --git a/apps/web/components/apps/InstallAppButtonChild.tsx b/apps/web/components/apps/InstallAppButtonChild.tsx index c7bb898465..fdeeea5f95 100644 --- a/apps/web/components/apps/InstallAppButtonChild.tsx +++ b/apps/web/components/apps/InstallAppButtonChild.tsx @@ -122,7 +122,7 @@ export const InstallAppButtonChild = ({ data-testid={team.isUser ? "install-app-button-personal" : "anything else"} key={team.id} disabled={isInstalled} - StartIcon={(props) => ( + StartIcon={(props: { className?: string }) => ( (eventType?.aiPhoneCallConfig?.enabled ?? false); + const formMethods = useFormContext(); + + const isOrg = !!session.data?.user?.org?.id; + + if (session.status === "loading") return <>; + + return ( + +
+ {!isOrg || !isTeamEvent ? ( + {t("upgrade")}} + /> + ) : ( +
+ { + if (!e) { + formMethods.setValue("aiPhoneCallConfig.enabled", false, { + shouldDirty: true, + }); + setAIEventState(false); + } else { + formMethods.setValue("aiPhoneCallConfig.enabled", true, { + shouldDirty: true, + }); + setAIEventState(true); + } + }}> +
+ {aiEventState && } +
+
+
+ )} +
+
+ ); +} + +const AISettings = ({ eventType }: { eventType: EventTypeSetup }) => { + const { t } = useLocale(); + + const formMethods = useFormContext(); + const [calApiKey, setCalApiKey] = useState(""); + + const createCallMutation = trpc.viewer.organizations.createPhoneCall.useMutation({ + onSuccess: (data) => { + if (!!data?.call_id) { + showToast("Phone Call Created successfully", "success"); + } + }, + onError: (err) => { + showToast(t("something_went_wrong"), "error"); + }, + }); + + // const [createModalOpen, setCreateModalOpen] = useState(false); + + // const NewPhoneButton = () => { + // const { t } = useLocale(); + // return ( + // + // ); + // }; + + // v1 will require the user to log in to Retellai.com to create a phone number, and an agent and + // authorize it with the Cal.com API key / OAuth + // const retellAuthorized = true; // TODO: call retellAPI here + + const handleSubmit = async () => { + try { + const values = formMethods.getValues("aiPhoneCallConfig"); + + const data = await AIPhoneSettingSchema.parseAsync({ + ...values, + eventTypeId: eventType.id, + calApiKey, + }); + + createCallMutation.mutate(data); + } catch (err) { + if (err instanceof z.ZodError) { + const fieldName = err.issues?.[0]?.path?.[0]; + const message = err.issues?.[0]?.message; + showToast(`Error on ${fieldName}: ${message} `, "error"); + } else { + showToast(t("something_went_wrong"), "error"); + } + } + }; + + return ( +
+
+ <> + + { + return ( + { + onChange(val); + }} + /> + ); + }} + /> + + + { + return ( + { + onChange(val); + }} + /> + ); + }} + /> + + + + + + + { + setCalApiKey(e.target.value); + }} + /> + + + { + formMethods.setValue("aiPhoneCallConfig.generalPrompt", e.target.value, { shouldDirty: true }); + }} + /> + + { + formMethods.setValue("aiPhoneCallConfig.beginMessage", e.target.value, { shouldDirty: true }); + }} + /> + + + + {/* TODO: + Want to automate outgoing phone calls? Read our{" "} + + API docs + {" "} + and learn how to build workflows. + */} +
+ + {/* TODO: + <> + + + +
+ } + /> + !isOpen && setCreateModalOpen(false)}> + +
+ +
+
+
+ + */} + + ); +}; diff --git a/apps/web/components/eventtype/EventAITab.tsx b/apps/web/components/eventtype/EventAITab.tsx new file mode 100644 index 0000000000..df7f1eb92d --- /dev/null +++ b/apps/web/components/eventtype/EventAITab.tsx @@ -0,0 +1,10 @@ +import type { EventTypeSetupProps } from "pages/event-types/[type]"; + +import AIEventController from "./AIEventController"; + +export const EventAITab = ({ + eventType, + isTeamEvent, +}: Pick & { isTeamEvent: boolean }) => { + return ; +}; diff --git a/apps/web/components/eventtype/EventTypeSingleLayout.tsx b/apps/web/components/eventtype/EventTypeSingleLayout.tsx index 8de2e224ab..583b83e76e 100644 --- a/apps/web/components/eventtype/EventTypeSingleLayout.tsx +++ b/apps/web/components/eventtype/EventTypeSingleLayout.tsx @@ -44,6 +44,7 @@ import { Repeat, Grid, Zap, + Sparkles, Users, ExternalLink, Code, @@ -283,6 +284,15 @@ function EventTypeSingleLayout({ info: `${activeWebhooksNumber} ${t("active")}`, }); } + const hidden = true; // hidden while in alpha trial. you can access it with tabName=ai + if (team && hidden) { + navigation.push({ + name: "Cal.ai", + href: `/event-types/${eventType.id}?tabName=ai`, + icon: Sparkles, + info: "cal_ai_event_tab_description", // todo `cal_ai_event_tab_description`, + }); + } return navigation; }, [ t, diff --git a/apps/web/components/ui/LinkIconButton.tsx b/apps/web/components/ui/LinkIconButton.tsx index ee1bae86c9..985925b594 100644 --- a/apps/web/components/ui/LinkIconButton.tsx +++ b/apps/web/components/ui/LinkIconButton.tsx @@ -1,9 +1,11 @@ import React from "react"; +import type { LucideIcon } from "@calcom/ui/components/icon"; + import type { SVGComponent } from "@lib/types/SVGComponent"; interface LinkIconButtonProps extends React.ButtonHTMLAttributes { - Icon: SVGComponent; + Icon: SVGComponent | LucideIcon; } export default function LinkIconButton(props: LinkIconButtonProps) { diff --git a/apps/web/modules/event-types/views/event-types-single-view.tsx b/apps/web/modules/event-types/views/event-types-single-view.tsx index 254d9271a3..20886b38e4 100644 --- a/apps/web/modules/event-types/views/event-types-single-view.tsx +++ b/apps/web/modules/event-types/views/event-types-single-view.tsx @@ -34,6 +34,11 @@ import { EventTypeSingleLayout } from "@components/eventtype/EventTypeSingleLayo import { type PageProps } from "~/event-types/views/event-types-single-view.getServerSideProps"; +const DEFAULT_PROMPT_VALUE = + "## You are helping user set up a call with the support team. The appointment is 15 min long. You are a pleasant and friendly.\n\n## Style Guardrails\nBe Concise: Respond succinctly, addressing one topic at most.\nEmbrace Variety: Use diverse language and rephrasing to enhance clarity without repeating content.\nBe Conversational: Use everyday language, making the chat feel like talking to a friend.\nBe Proactive: Lead the conversation, often wrapping up with a question or next-step suggestion.\nAvoid multiple questions in a single response.\nGet clarity: If the user only partially answers a question, or if the answer is unclear, keep asking to get clarity.\nUse a colloquial way of referring to the date (like Friday, Jan 14th, or Tuesday, Jan 12th, 2024 at 8am).\nIf you are saying a time like 8:00 AM, just say 8 AM and emit the trailing zeros.\n\n## Response Guideline\nAdapt and Guess: Try to understand transcripts that may contain transcription errors. Avoid mentioning \"transcription error\" in the response.\nStay in Character: Keep conversations within your role'''s scope, guiding them back creatively without repeating.\nEnsure Fluid Dialogue: Respond in a role-appropriate, direct manner to maintain a smooth conversation flow.\n\n## Schedule Rule\nCurrent time is {{current_time}}. You only schedule time in current calendar year, you cannot schedule time that'''s in the past.\n\n## Task Steps\n1. I am here to learn more about your issue and help schedule an appointment with our support team.\n2. Ask for user name and email. Confirm the name and email with user by reading it back to user.\n3. Ask user for \"When would you want to meet with one of our representive\".\n4. Call function check_availability to check for availability in the user provided time range.\n - if availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot.\n - if availability does not exist, ask user to select another time range for the appointment, repeat this step 3.\n4. Confirm the date and time selected by user: \"Just to confirm, you want to book the appointment at ...\".\n6. Once confirmed, call function book_appointment to book the appointment.\n - if booking returned booking detail, it means booking is successful, proceed to step 7.\n - if booking returned error message, let user know why the booking was not successful, and maybe start over with step 3.\n7. Inform the user booking is successful, and ask if user have any questions. Answer them if there are any.\n8. After all questions answered, call function end_call to hang up."; + +const DEFAULT_BEGIN_MESSAGE = "Hi. How are you doing?"; + // These can't really be moved into calcom/ui due to the fact they use infered getserverside props typings; const EventSetupTab = dynamic(() => import("@components/eventtype/EventSetupTab").then((mod) => mod.EventSetupTab) @@ -73,6 +78,8 @@ const EventWebhooksTab = dynamic(() => import("@components/eventtype/EventWebhooksTab").then((mod) => mod.EventWebhooksTab) ); +const EventAITab = dynamic(() => import("@components/eventtype/EventAITab").then((mod) => mod.EventAITab)); + const ManagedEventTypeDialog = dynamic(() => import("@components/eventtype/ManagedEventDialog")); export type Host = { isFixed: boolean; userId: number; priority: number }; @@ -92,6 +99,7 @@ const querySchema = z.object({ "advanced", "workflows", "webhooks", + "ai", ]) .optional() .default("setup"), @@ -243,6 +251,14 @@ const EventTypePage = (props: EventTypeSetupProps) => { })), seatsPerTimeSlotEnabled: eventType.seatsPerTimeSlot, assignAllTeamMembers: eventType.assignAllTeamMembers, + aiPhoneCallConfig: { + generalPrompt: eventType.aiPhoneCallConfig?.generalPrompt ?? DEFAULT_PROMPT_VALUE, + enabled: eventType.aiPhoneCallConfig?.enabled, + beginMessage: eventType.aiPhoneCallConfig?.beginMessage ?? DEFAULT_BEGIN_MESSAGE, + guestName: eventType.aiPhoneCallConfig?.guestName, + yourPhoneNumber: eventType.aiPhoneCallConfig?.yourPhoneNumber, + numberToCall: eventType.aiPhoneCallConfig?.numberToCall, + }, }; }, [eventType, periodDates, metadata]); const formMethods = useForm({ @@ -375,6 +391,7 @@ const EventTypePage = (props: EventTypeSetupProps) => { /> ), webhooks: , + ai: , } as const; const isObject = (value: T): boolean => { return value !== null && typeof value === "object" && !Array.isArray(value); diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 77570ac465..35f8ba2684 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -81,6 +81,8 @@ "payment": "Payment", "missing_card_fields": "Missing card fields", "pay_now": "Pay now", + "general_prompt": "General Prompt", + "begin_message":"Begin Message", "codebase_has_to_stay_opensource": "The codebase has to stay open source, whether it was modified or not", "cannot_repackage_codebase": "You can not repackage or sell the codebase", "acquire_license": "Acquire a commercial license to remove these terms by emailing", @@ -207,6 +209,7 @@ "2fa_confirm_current_password": "Confirm your current password to get started.", "2fa_scan_image_or_use_code": "Scan the image below with the authenticator app on your phone or manually enter the text code instead.", "text": "Text", + "your_phone_number":"Your Phone Number", "multiline_text": "Multiline Text", "number": "Number", "checkbox": "Checkbox", @@ -693,6 +696,7 @@ "multiple_duration_mins": "{{count}} $t(minute_timeUnit)", "multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)", "minutes": "Minutes", + "use_cal_ai_to_make_call_description": "Use Cal.ai to get an AI powered phone number or make calls to guests.", "round_robin": "Round Robin", "round_robin_description": "Cycle meetings between multiple team members.", "managed_event": "Managed Event", @@ -1277,6 +1281,7 @@ "upgrade": "Upgrade", "upgrade_to_access_recordings_title": "Upgrade to access recordings", "upgrade_to_access_recordings_description": "Recordings are only available as part of our teams plan. Upgrade to start recording your calls", + "upgrade_to_cal_ai_phone_number_description":"Upgrade to Enterprise to generate an AI Agent phone number that can call guests to schedule calls", "recordings_are_part_of_the_teams_plan": "Recordings are part of the teams plan", "team_feature_teams": "This is a Team feature. Upgrade to Team to see your team's availability.", "team_feature_workflows": "This is a Team feature. Upgrade to Team to automate your event notifications and reminders with Workflows.", @@ -2249,6 +2254,8 @@ "troubleshooter_tooltip": "Open the troubleshooter and figure out what is wrong with your schedule", "need_help": "Need help?", "troubleshooter": "Troubleshooter", + "number_to_call": "Number to Call", + "guest_name": "Guest Name", "please_install_a_calendar": "Please install a calendar", "instant_tab_title": "Instant Booking", "instant_event_tab_description": "Let people book immediately", @@ -2256,6 +2263,7 @@ "dont_want_to_wait": "Don't want to wait?", "meeting_started": "Meeting Started", "pay_and_book": "Pay to book", + "cal_ai_event_tab_description":"Let AI Agents book you", "booking_not_found_error": "Could not find booking", "booking_seats_full_error": "Booking seats are full", "missing_payment_credential_error": "Missing payment credentials", @@ -2330,5 +2338,6 @@ "lock_org_users_eventtypes": "Lock individual event type creation", "lock_org_users_eventtypes_description": "Prevent members from creating their own event types.", "cookie_consent_checkbox": "I consent to our privacy policy and cookie usage", + "make_a_call": "Make a Call", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/package.json b/package.json index 46b04c292e..bff545b03f 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ }, "dependencies": { "eslint": "^8.34.0", - "lucide-react": "^0.171.0", + "lucide-react": "^0.363.0", "turbo": "^1.10.1" }, "resolutions": { diff --git a/packages/app-store/giphy/components/SearchDialog.tsx b/packages/app-store/giphy/components/SearchDialog.tsx index cd05adf201..158965a856 100644 --- a/packages/app-store/giphy/components/SearchDialog.tsx +++ b/packages/app-store/giphy/components/SearchDialog.tsx @@ -6,6 +6,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { SVGComponent } from "@calcom/types/SVGComponent"; import { Alert, Button, Dialog, DialogClose, DialogContent, DialogFooter, Input } from "@calcom/ui"; import { Link, Search } from "@calcom/ui/components/icon"; +import type { LucideIcon } from "@calcom/ui/components/icon"; interface ISearchDialog { isOpenDialog: boolean; @@ -85,7 +86,7 @@ export const SearchDialog = (props: ISearchDialog) => { return null; }; - const renderTab = (Icon: SVGComponent, text: string, mode: Mode) => ( + const renderTab = (Icon: SVGComponent | LucideIcon, text: string, mode: Mode) => (
| string; + icon?: React.FC<{ className: string }> | string | LucideIcon; children: React.ReactNode; // Emphasises the text in the block. For now only // applying in dark mode. diff --git a/packages/features/bookings/components/event-meta/getPayIcon.ts b/packages/features/bookings/components/event-meta/getPayIcon.ts index 0219b86944..2c44c4cc39 100644 --- a/packages/features/bookings/components/event-meta/getPayIcon.ts +++ b/packages/features/bookings/components/event-meta/getPayIcon.ts @@ -1,5 +1,7 @@ import { CreditCard, Zap } from "lucide-react"; -export function getPayIcon(currency: string): React.FC<{ className: string }> | string { +import type { LucideIcon } from "@calcom/ui/components/icon"; + +export function getPayIcon(currency: string): React.FC<{ className: string }> | string | LucideIcon { return currency !== "BTC" ? CreditCard : Zap; } diff --git a/packages/features/bookings/components/event-meta/getPriceIcon.ts b/packages/features/bookings/components/event-meta/getPriceIcon.ts index 4e122f59c5..5a1e9a1c76 100644 --- a/packages/features/bookings/components/event-meta/getPriceIcon.ts +++ b/packages/features/bookings/components/event-meta/getPriceIcon.ts @@ -1,7 +1,8 @@ import { CreditCard } from "lucide-react"; +import type { LucideIcon } from "@calcom/ui/components/icon"; import { SatSymbol } from "@calcom/ui/components/icon/SatSymbol"; -export function getPriceIcon(currency: string): React.FC<{ className: string }> | string { +export function getPriceIcon(currency: string): React.FC<{ className: string }> | string | LucideIcon { return currency !== "BTC" ? CreditCard : (SatSymbol as React.FC<{ className: string }>); } diff --git a/packages/features/ee/workflows/components/EmptyScreen.tsx b/packages/features/ee/workflows/components/EmptyScreen.tsx index 0f9f8fbd34..db54ad6c26 100644 --- a/packages/features/ee/workflows/components/EmptyScreen.tsx +++ b/packages/features/ee/workflows/components/EmptyScreen.tsx @@ -6,9 +6,10 @@ import { trpc } from "@calcom/trpc/react"; import type { SVGComponent } from "@calcom/types/SVGComponent"; import { CreateButtonWithTeamsList, EmptyScreen as ClassicEmptyScreen, showToast } from "@calcom/ui"; import { Mail, Smartphone, Zap } from "@calcom/ui/components/icon"; +import type { LucideIcon } from "@calcom/ui/components/icon"; type WorkflowExampleType = { - Icon: SVGComponent; + Icon: SVGComponent | LucideIcon; text: string; }; diff --git a/packages/features/eventtypes/lib/getPublicEvent.ts b/packages/features/eventtypes/lib/getPublicEvent.ts index df1343fcc2..05b138ad67 100644 --- a/packages/features/eventtypes/lib/getPublicEvent.ts +++ b/packages/features/eventtypes/lib/getPublicEvent.ts @@ -54,6 +54,7 @@ const publicEventSelect = Prisma.validator()({ eventName: true, slug: true, isInstantEvent: true, + aiPhoneCallConfig: true, schedulingType: true, length: true, locations: true, @@ -302,6 +303,7 @@ export const getPublicEvent = async ( }, isDynamic: false, isInstantEvent: eventWithUserProfiles.isInstantEvent, + aiPhoneCallConfig: eventWithUserProfiles.aiPhoneCallConfig, assignAllTeamMembers: event.assignAllTeamMembers, }; }; diff --git a/packages/features/eventtypes/lib/types.ts b/packages/features/eventtypes/lib/types.ts index 042baa59ca..8363cb2230 100644 --- a/packages/features/eventtypes/lib/types.ts +++ b/packages/features/eventtypes/lib/types.ts @@ -58,6 +58,14 @@ export type FormValues = { credentialId?: number; teamName?: string; }[]; + aiPhoneCallConfig: { + generalPrompt: string; + enabled: boolean; + beginMessage: string; + yourPhoneNumber: string; + numberToCall: string; + guestName: string; + }; customInputs: CustomInputParsed[]; schedule: number | null; periodType: PeriodType; diff --git a/packages/features/insights/filters/FilterType.tsx b/packages/features/insights/filters/FilterType.tsx index 38e8886fc9..b29ae93609 100644 --- a/packages/features/insights/filters/FilterType.tsx +++ b/packages/features/insights/filters/FilterType.tsx @@ -8,11 +8,12 @@ import { DropdownMenuTrigger, Tooltip, } from "@calcom/ui"; +import type { LucideIcon } from "@calcom/ui/components/icon"; import { Plus, Link, User, Check } from "@calcom/ui/components/icon"; import { useFilterContext } from "../context/provider"; -type Option = { value: "event-type" | "user"; label: string; StartIcon?: SVGComponent }; +type Option = { value: "event-type" | "user"; label: string; StartIcon?: SVGComponent | LucideIcon }; export const FilterType = () => { const { t } = useLocale(); diff --git a/packages/features/shell/Shell.tsx b/packages/features/shell/Shell.tsx index aa5a1b8bad..1208ff3559 100644 --- a/packages/features/shell/Shell.tsx +++ b/packages/features/shell/Shell.tsx @@ -79,6 +79,7 @@ import { Tooltip, useCalcomTheme, } from "@calcom/ui"; +import type { LucideIcon } from "@calcom/ui/components/icon"; import { ArrowLeft, ArrowRight, @@ -100,6 +101,7 @@ import { Settings, User as UserIcon, Users, + Sparkles, Zap, Check, } from "@calcom/ui/components/icon"; @@ -504,7 +506,7 @@ function UserDropdown({ small }: UserDropdownProps) { ( + StartIcon={(props: { className?: string }) => (