diff --git a/apps/web/components/eventtype/EventSetupTab.tsx b/apps/web/components/eventtype/EventSetupTab.tsx
index 82972e0e44..2d5f588fa4 100644
--- a/apps/web/components/eventtype/EventSetupTab.tsx
+++ b/apps/web/components/eventtype/EventSetupTab.tsx
@@ -1,16 +1,18 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
+import { Trans } from "next-i18next";
+import Link from "next/link";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { Controller, useForm, useFormContext } from "react-hook-form";
import { MultiValue } from "react-select";
import { z } from "zod";
-import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
+import { EventLocationType, getEventLocationType, MeetLocationType } from "@calcom/app-store/locations";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
-import { Button, Icon, Label, Select, Skeleton, TextField, SettingsToggle } from "@calcom/ui";
+import { Button, Icon, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui";
import { slugify } from "@lib/slugify";
@@ -195,6 +197,23 @@ export const EventSetupTab = (
);
})}
+ {validLocations.some((location) => location.type === MeetLocationType) && (
+
+
+
+
+ The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
+ Change it{" "}
+
+ here.
+ {" "}
+ We will fall back to Cal video if you do not change it.
+
+
+
+ )}
{validLocations.length > 0 && validLocations.length !== locationOptions.length && (
setShowLocationModal(true)}>
@@ -326,6 +345,7 @@ export const EventSetupTab = (
{t("location")}
+
{
uid: "xxyPr4cg2xx4XoS2KeMEQy",
metadata: {},
recurringEvent: null,
+ appsStatus: [
+ {
+ appName: "Outlook Calendar",
+ type: "office365_calendar",
+ success: 1,
+ failures: 0,
+ errors: [],
+ warnings: [],
+ },
+ {
+ appName: "Google Meet",
+ type: "conferencing",
+ success: 0,
+ failures: 1,
+ errors: [],
+ warnings: ["In order to use Google Meet you must set your destination calendar to a Google Calendar"],
+ },
+ ],
};
req.statusCode = 200;
@@ -42,9 +60,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader("Content-Type", "text/html");
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
res.write(
- renderEmail("OrganizerRequestEmail", {
- attendee: evt.attendees[0],
+ renderEmail("OrganizerScheduledEmail", {
calEvent: evt,
+ attendee: evt.organizer,
})
);
res.end();
diff --git a/apps/web/pages/api/integrations/[...args].ts b/apps/web/pages/api/integrations/[...args].ts
index 6335b51468..8e216f89f8 100644
--- a/apps/web/pages/api/integrations/[...args].ts
+++ b/apps/web/pages/api/integrations/[...args].ts
@@ -2,11 +2,11 @@ import { NextApiRequest, NextApiResponse } from "next";
import type { Session } from "next-auth";
import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath";
+import { getSession } from "@calcom/lib/auth";
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
import prisma from "@calcom/prisma";
import type { AppDeclarativeHandler, AppHandler } from "@calcom/types/AppHandler";
-import { getSession } from "@lib/auth";
import { HttpError } from "@lib/core/http/error";
const defaultIntegrationAddHandler = async ({
diff --git a/apps/web/pages/apps/[slug]/index.tsx b/apps/web/pages/apps/[slug]/index.tsx
index 674b98929b..a9506b6212 100644
--- a/apps/web/pages/apps/[slug]/index.tsx
+++ b/apps/web/pages/apps/[slug]/index.tsx
@@ -5,6 +5,7 @@ import { GetStaticPaths, GetStaticPropsContext } from "next";
import path from "path";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
+import ExisitingGoogleCal from "@calcom/app-store/googlevideo/components/ExistingGoogleCal";
import prisma from "@calcom/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -36,7 +37,12 @@ function SingleAppPage({ data, source }: inferSSRProps) {
images={source.data?.items as string[] | undefined}
// tos="https://zoom.us/terms"
// privacy="https://zoom.us/privacy"
- body={
}
+ body={
+ <>
+ {data.slug === "google-meet" && }
+
+ >
+ }
/>
);
}
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index acb1599deb..9be4879322 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -1456,6 +1456,7 @@
"enter_option": "Enter Option {{index}}",
"add_an_option": "Add an option",
"radio": "Radio",
+ "google_meet_warning": "In order to use Google Meet you must set your destination calendar to a Google Calendar",
"individual":"Individual",
"all_bookings_filter_label":"All Bookings",
"all_users_filter_label":"All Users",
@@ -1490,5 +1491,11 @@
"disable_success_page":"Disable Success Page (only works if you have a redirect URL)",
"invalid_admin_password": "You are set as an admin but you do not have a password length of at least 15 characters",
"change_password_admin": "Change Password to gain admin access",
- "username_already_taken": "Username is already taken"
+ "username_already_taken": "Username is already taken",
+ "requires_google_calendar": "This app requires a Google Calendar connection",
+ "connected_google_calendar": "You have connected a Google Calendar account.",
+ "using_meet_requires_calendar": "Using Google Meet requires a connected Google Calendar",
+ "continue_to_install_google_calendar": "Continue to install Google Calendar",
+ "install_google_meet": "Install Google Meet",
+ "install_google_calendar": "Install Google Calendar"
}
diff --git a/packages/app-store/_utils/useAddAppMutation.ts b/packages/app-store/_utils/useAddAppMutation.ts
index 3d5836bf2c..a5a67ad4d0 100644
--- a/packages/app-store/_utils/useAddAppMutation.ts
+++ b/packages/app-store/_utils/useAddAppMutation.ts
@@ -20,7 +20,8 @@ type CustomUseMutationOptions =
type AddAppMutationData = { setupPending: boolean } | void;
type UseAddAppMutationOptions = CustomUseMutationOptions & {
- onSuccess: (data: AddAppMutationData) => void;
+ onSuccess?: (data: AddAppMutationData) => void;
+ installGoogleVideo?: boolean;
returnTo?: string;
};
@@ -42,6 +43,10 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
if (type?.endsWith("_other_calendar")) {
type = type.split("_other_calendar")[0];
}
+
+ if (options?.installGoogleVideo && type !== "google_calendar")
+ throw new Error("Could not install Google Meet");
+
const state: IntegrationOAuthCallbackState = {
returnTo:
returnTo ||
@@ -50,6 +55,7 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
{ variant: variables && variables.variant, slug: variables && variables.slug },
location.search
),
+ ...(type === "google_calendar" && { installGoogleVideo: options?.installGoogleVideo }),
};
const stateStr = encodeURIComponent(JSON.stringify(state));
const searchParams = `?state=${stateStr}`;
diff --git a/packages/app-store/apps.browser.generated.tsx b/packages/app-store/apps.browser.generated.tsx
index dd1eae9805..7bfb735ced 100644
--- a/packages/app-store/apps.browser.generated.tsx
+++ b/packages/app-store/apps.browser.generated.tsx
@@ -13,6 +13,7 @@ export const InstallAppButtonMap = {
exchange2016calendar: dynamic(() => import("./exchange2016calendar/components/InstallAppButton")),
exchangecalendar: dynamic(() => import("./exchangecalendar/components/InstallAppButton")),
googlecalendar: dynamic(() => import("./googlecalendar/components/InstallAppButton")),
+ googlevideo: dynamic(() => import("./googlevideo/components/InstallAppButton")),
hubspot: dynamic(() => import("./hubspot/components/InstallAppButton")),
huddle01video: dynamic(() => import("./huddle01video/components/InstallAppButton")),
jitsivideo: dynamic(() => import("./jitsivideo/components/InstallAppButton")),
diff --git a/packages/app-store/apps.server.generated.ts b/packages/app-store/apps.server.generated.ts
index d3f7e05c01..71d7faa024 100644
--- a/packages/app-store/apps.server.generated.ts
+++ b/packages/app-store/apps.server.generated.ts
@@ -17,6 +17,7 @@ export const apiHandlers = {
ga4: import("./ga4/api"),
giphy: import("./giphy/api"),
googlecalendar: import("./googlecalendar/api"),
+ googlevideo: import("./googlevideo/api"),
hubspot: import("./hubspot/api"),
huddle01video: import("./huddle01video/api"),
jitsivideo: import("./jitsivideo/api"),
diff --git a/packages/app-store/googlecalendar/api/callback.ts b/packages/app-store/googlecalendar/api/callback.ts
index 5e25e95dfa..ce509954df 100644
--- a/packages/app-store/googlecalendar/api/callback.ts
+++ b/packages/app-store/googlecalendar/api/callback.ts
@@ -1,7 +1,7 @@
import { google } from "googleapis";
import type { NextApiRequest, NextApiResponse } from "next";
-import { WEBAPP_URL } from "@calcom/lib/constants";
+import { WEBAPP_URL, CAL_URL } from "@calcom/lib/constants";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import prisma from "@calcom/prisma";
@@ -14,6 +14,8 @@ let client_secret = "";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { code } = req.query;
+ const state = decodeOAuthState(req);
+
if (code && typeof code !== "string") {
res.status(400).json({ message: "`code` must be a string" });
return;
@@ -47,7 +49,31 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
appId: "google-calendar",
},
});
- const state = decodeOAuthState(req);
+
+ if (state?.installGoogleVideo) {
+ const existingGoogleMeetCredential = await prisma.credential.findFirst({
+ where: {
+ userId: req.session.user.id,
+ type: "google_video",
+ },
+ });
+
+ if (!existingGoogleMeetCredential) {
+ await prisma.credential.create({
+ data: {
+ type: "google_video",
+ key: {},
+ userId: req.session.user.id,
+ appId: "google-meet",
+ },
+ });
+
+ res.redirect(
+ getSafeRedirectUrl(CAL_URL + "/apps/installed/conferencing?hl=google-meet") ??
+ getInstalledAppPath({ variant: "conferencing", slug: "google-meet" })
+ );
+ }
+ }
res.redirect(
getSafeRedirectUrl(state?.returnTo) ??
getInstalledAppPath({ variant: "calendar", slug: "google-calendar" })
diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts
index a420bcefc3..9e516b05e3 100644
--- a/packages/app-store/googlecalendar/lib/CalendarService.ts
+++ b/packages/app-store/googlecalendar/lib/CalendarService.ts
@@ -1,6 +1,7 @@
import { Prisma } from "@prisma/client";
import { calendar_v3, google } from "googleapis";
+import { MeetLocationType } from "@calcom/app-store/locations";
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
import CalendarService from "@calcom/lib/CalendarService";
import logger from "@calcom/lib/logger";
@@ -105,7 +106,7 @@ export default class GoogleCalendarService implements Calendar {
payload["location"] = getLocation(calEventRaw);
}
- if (calEventRaw.conferenceData && calEventRaw.location === "integrations:google:meet") {
+ if (calEventRaw.conferenceData && calEventRaw.location === MeetLocationType) {
payload["conferenceData"] = calEventRaw.conferenceData;
}
const calendar = google.calendar({
@@ -195,7 +196,7 @@ export default class GoogleCalendarService implements Calendar {
payload["location"] = getLocation(event);
}
- if (event.conferenceData && event.location === "integrations:google:meet") {
+ if (event.conferenceData && event.location === MeetLocationType) {
payload["conferenceData"] = event.conferenceData;
}
@@ -225,7 +226,7 @@ export default class GoogleCalendarService implements Calendar {
return reject(err);
}
- if (evt && evt.data.id && evt.data.hangoutLink && event.location === "integrations:google:meet") {
+ if (evt && evt.data.id && evt.data.hangoutLink && event.location === MeetLocationType) {
calendar.events.patch({
// Update the same event but this time we know the hangout link
calendarId: selectedCalendar,
diff --git a/packages/app-store/googlevideo/DESCRIPTION.md b/packages/app-store/googlevideo/DESCRIPTION.md
index 99b9d0490a..b0abb0e118 100644
--- a/packages/app-store/googlevideo/DESCRIPTION.md
+++ b/packages/app-store/googlevideo/DESCRIPTION.md
@@ -5,3 +5,5 @@ items:
---
Google Meet is Google's web-based video conferencing platform, designed to compete with major conferencing platforms.
+
+Note this requires a connected Google Calendar.
diff --git a/packages/app-store/googlevideo/_metadata.ts b/packages/app-store/googlevideo/_metadata.ts
index 4ea165306b..07becf2e80 100644
--- a/packages/app-store/googlevideo/_metadata.ts
+++ b/packages/app-store/googlevideo/_metadata.ts
@@ -21,7 +21,7 @@ export const metadata = {
trending: false,
url: "https://cal.com/",
verified: true,
- isGlobal: true,
+ isGlobal: false,
email: "help@cal.com",
appData: {
location: {
diff --git a/packages/app-store/googlevideo/api/_getAdd.ts b/packages/app-store/googlevideo/api/_getAdd.ts
new file mode 100644
index 0000000000..40dc19847d
--- /dev/null
+++ b/packages/app-store/googlevideo/api/_getAdd.ts
@@ -0,0 +1,40 @@
+import type { NextApiRequest, NextApiResponse } from "next";
+
+import prisma from "@calcom/prisma";
+
+import getInstalledAppPath from "../../_utils/getInstalledAppPath";
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ if (!req.session?.user?.id) {
+ return res.status(401).json({ message: "You must be logged in to do this" });
+ }
+ const appType = "google_video";
+ try {
+ const alreadyInstalled = await prisma.credential.findFirst({
+ where: {
+ type: appType,
+ userId: req.session.user.id,
+ },
+ });
+ if (alreadyInstalled) {
+ throw new Error("Already installed");
+ }
+ const installation = await prisma.credential.create({
+ data: {
+ type: appType,
+ key: {},
+ userId: req.session.user.id,
+ appId: "google-meet",
+ },
+ });
+ if (!installation) {
+ throw new Error("Unable to create user credential for google_video");
+ }
+ } catch (error: unknown) {
+ if (error instanceof Error) {
+ return res.status(500).json({ message: error.message });
+ }
+ return res.status(500);
+ }
+ return res.status(200).json({ url: getInstalledAppPath({ variant: "conferencing", slug: "google-meet" }) });
+}
diff --git a/packages/app-store/googlevideo/api/add.ts b/packages/app-store/googlevideo/api/add.ts
new file mode 100644
index 0000000000..912126e232
--- /dev/null
+++ b/packages/app-store/googlevideo/api/add.ts
@@ -0,0 +1,5 @@
+import { defaultHandler } from "@calcom/lib/server";
+
+export default defaultHandler({
+ GET: import("./_getAdd"),
+});
diff --git a/packages/app-store/googlevideo/api/index.ts b/packages/app-store/googlevideo/api/index.ts
new file mode 100644
index 0000000000..4c0d2ead01
--- /dev/null
+++ b/packages/app-store/googlevideo/api/index.ts
@@ -0,0 +1 @@
+export { default as add } from "./add";
diff --git a/packages/app-store/googlevideo/components/ExistingGoogleCal.tsx b/packages/app-store/googlevideo/components/ExistingGoogleCal.tsx
new file mode 100644
index 0000000000..b23f5c43c2
--- /dev/null
+++ b/packages/app-store/googlevideo/components/ExistingGoogleCal.tsx
@@ -0,0 +1,43 @@
+import { Trans } from "next-i18next";
+import Link from "next/link";
+
+import { CAL_URL } from "@calcom/lib/constants";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { trpc } from "@calcom/trpc";
+import { Icon, SkeletonText } from "@calcom/ui";
+
+const ExistingGoogleCal = () => {
+ const { t } = useLocale();
+ const { isLoading, data: hasGoogleCal } = trpc.viewer.appsRouter.checkForGCal.useQuery();
+
+ return (
+
+
+
+
+
+
+
{t("requires_google_calendar")}
+
+ <>
+ {isLoading ? (
+
+ ) : hasGoogleCal ? (
+ t("connected_google_calendar")
+ ) : (
+
+ Please connect your Google Calendar account{" "}
+
+ here
+
+
+ )}
+ >
+
+
+
+
+ );
+};
+
+export default ExistingGoogleCal;
diff --git a/packages/app-store/googlevideo/components/InstallAppButton.tsx b/packages/app-store/googlevideo/components/InstallAppButton.tsx
new file mode 100644
index 0000000000..dff685c3b7
--- /dev/null
+++ b/packages/app-store/googlevideo/components/InstallAppButton.tsx
@@ -0,0 +1,64 @@
+import { useState, useEffect } from "react";
+
+import type { InstallAppButtonProps } from "@calcom/app-store/types";
+import useApp from "@calcom/lib/hooks/useApp";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import type { DialogProps } from "@calcom/ui";
+import { Button } from "@calcom/ui";
+import { Dialog, DialogClose, DialogContent, DialogFooter } from "@calcom/ui";
+
+import useAddAppMutation from "../../_utils/useAddAppMutation";
+
+export default function InstallAppButton(props: InstallAppButtonProps) {
+ const [showWarningDialog, setShowWarningDialog] = useState(false);
+
+ return (
+ <>
+ {props.render({
+ onClick() {
+ setShowWarningDialog(true);
+ // mutation.mutate("");
+ },
+ disabled: showWarningDialog,
+ })}
+
+ >
+ );
+}
+
+function WarningDialog(props: DialogProps) {
+ const { t } = useLocale();
+ const googleCalendarData = useApp("google-calendar");
+ const googleCalendarPresent = googleCalendarData.data?.isInstalled;
+
+ const mutation = useAddAppMutation(googleCalendarPresent ? "google_video" : "google_calendar", {
+ installGoogleVideo: !googleCalendarPresent,
+ });
+
+ return (
+
+
+
+ <>
+ {
+ props.onOpenChange?.(false);
+ }}>
+ {t("cancel")}
+
+
+ mutation.mutate("")}>
+ {googleCalendarPresent ? t("install_google_meet") : t("install_google_calendar")}
+
+ >
+
+
+
+ );
+}
diff --git a/packages/app-store/googlevideo/components/index.ts b/packages/app-store/googlevideo/components/index.ts
new file mode 100644
index 0000000000..0d6008d4ca
--- /dev/null
+++ b/packages/app-store/googlevideo/components/index.ts
@@ -0,0 +1 @@
+export { default as InstallAppButton } from "./InstallAppButton";
diff --git a/packages/app-store/locations.ts b/packages/app-store/locations.ts
index 5789eb830e..47b50c8b5e 100644
--- a/packages/app-store/locations.ts
+++ b/packages/app-store/locations.ts
@@ -40,6 +40,8 @@ export type EventLocationType = DefaultEventLocationType | EventLocationTypeFrom
export const DailyLocationType = "integrations:daily";
+export const MeetLocationType = "integrations:google:meet";
+
export enum DefaultEventLocationTypeEnum {
/**
* Booker Address
diff --git a/packages/app-store/types.d.ts b/packages/app-store/types.d.ts
index 081887a80c..b586522960 100644
--- a/packages/app-store/types.d.ts
+++ b/packages/app-store/types.d.ts
@@ -7,6 +7,7 @@ import { ButtonProps } from "@calcom/ui";
export type IntegrationOAuthCallbackState = {
returnTo: string;
+ installGoogleVideo?: boolean;
};
export interface InstallAppButtonProps {
diff --git a/packages/core/EventManager.ts b/packages/core/EventManager.ts
index 2a9c15888b..af95df3bc6 100644
--- a/packages/core/EventManager.ts
+++ b/packages/core/EventManager.ts
@@ -5,6 +5,7 @@ import { z } from "zod";
import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
import { getEventLocationTypeFromApp } from "@calcom/app-store/locations";
+import { MeetLocationType } from "@calcom/app-store/locations";
import getApps from "@calcom/app-store/utils";
import prisma from "@calcom/prisma";
import { Attendee } from "@calcom/prisma/client";
@@ -28,7 +29,7 @@ import { createEvent, updateEvent } from "./CalendarManager";
import { createMeeting, updateMeeting } from "./videoClient";
export const isDedicatedIntegration = (location: string): boolean => {
- return location !== "integrations:google:meet" && location.includes("integrations:");
+ return location !== MeetLocationType && location.includes("integrations:");
};
export const getLocationRequestFromIntegration = (location: string) => {
@@ -102,9 +103,15 @@ export default class EventManager {
const evt = processLocation(event);
// Fallback to cal video if no location is set
if (!evt.location) evt["location"] = "integrations:daily";
+
+ // Fallback to Cal Video if Google Meet is selected w/o a Google Cal
+ if (evt.location === MeetLocationType && evt.destinationCalendar?.integration !== "google_calendar") {
+ evt["location"] = "integrations:daily";
+ }
const isDedicated = evt.location ? isDedicatedIntegration(evt.location) : null;
const results: Array>> = [];
+
// If and only if event type is a dedicated meeting, create a dedicated video meeting.
if (isDedicated) {
const result = await this.createVideoEvent(evt);
diff --git a/packages/emails/src/components/AppsStatus.tsx b/packages/emails/src/components/AppsStatus.tsx
index 67f43488ad..d7b35960e6 100644
--- a/packages/emails/src/components/AppsStatus.tsx
+++ b/packages/emails/src/components/AppsStatus.tsx
@@ -16,6 +16,7 @@ export const AppsStatus = (props: { calEvent: CalendarEvent; t: TFunction }) =>
{status.appName}{" "}
{status.success >= 1 && `✅ ${status.success > 1 ? `(x${status.success})` : ""}`}
+ {status.failures >= 1 && `❌ ${status.failures > 1 ? `(x${status.failures})` : ""}`}
{status.warnings && status.warnings.length >= 1 && (
{status.warnings.map((warning, i) => (
@@ -23,7 +24,6 @@ export const AppsStatus = (props: { calEvent: CalendarEvent; t: TFunction }) =>
))}
)}
- {status.failures >= 1 && `❌ ${status.failures > 1 ? `(x${status.failures})` : ""}`}
{status.errors.length >= 1 && (
{status.errors.map((error, i) => (
diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts
index 77ba0b2d65..74afb2bece 100644
--- a/packages/features/bookings/lib/handleNewBooking.ts
+++ b/packages/features/bookings/lib/handleNewBooking.ts
@@ -14,7 +14,9 @@ import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import z from "zod";
+import { metadata as GoogleMeetMetadata } from "@calcom/app-store/googlevideo/_metadata";
import { getLocationValueForDB, LocationObject } from "@calcom/app-store/locations";
+import { MeetLocationType } from "@calcom/app-store/locations";
import { handleEthSignature } from "@calcom/app-store/rainbow/utils/ethereum";
import { handlePayment } from "@calcom/app-store/stripepayment/lib/server";
import { getEventTypeAppData } from "@calcom/app-store/utils";
@@ -911,6 +913,38 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
const metadata: AdditionalInformation = {};
if (results.length) {
+ // Handle Google Meet results
+ // We use the original booking location since the evt location changes to daily
+ if (bookingLocation === MeetLocationType) {
+ const googleMeetResult = {
+ appName: GoogleMeetMetadata.name,
+ type: "conferencing",
+ uid: results[0].uid,
+ originalEvent: results[0].originalEvent,
+ };
+
+ const googleCalResult = results.find((result) => result.type === "google_calendar");
+
+ if (!googleCalResult) {
+ results.push({
+ ...googleMeetResult,
+ success: false,
+ calWarnings: [tOrganizer("google_meet_warning")],
+ });
+ }
+
+ if (googleCalResult?.createdEvent?.hangoutLink) {
+ results.push({
+ ...googleMeetResult,
+ success: true,
+ });
+ } else if (googleCalResult && !googleCalResult.createdEvent?.hangoutLink) {
+ results.push({
+ ...googleMeetResult,
+ success: false,
+ });
+ }
+ }
// TODO: Handle created event metadata more elegantly
metadata.hangoutLink = results[0].createdEvent?.hangoutLink;
metadata.conferenceData = results[0].createdEvent?.conferenceData;
diff --git a/packages/trpc/server/routers/viewer/apps.tsx b/packages/trpc/server/routers/viewer/apps.tsx
index f6097950dc..09f54bb453 100644
--- a/packages/trpc/server/routers/viewer/apps.tsx
+++ b/packages/trpc/server/routers/viewer/apps.tsx
@@ -10,7 +10,7 @@ import { getTranslation } from "@calcom/lib/server/i18n";
import { TRPCError } from "@trpc/server";
-import { authedAdminProcedure, router } from "../../trpc";
+import { authedAdminProcedure, authedProcedure, router } from "../../trpc";
interface FilteredApp {
name: string;
@@ -268,4 +268,13 @@ export const appsRouter = router({
},
});
}),
+ checkForGCal: authedProcedure.query(async ({ ctx }) => {
+ const gCalPresent = await ctx.prisma.credential.findFirst({
+ where: {
+ type: "google_calendar",
+ userId: ctx.user.id,
+ },
+ });
+ return !!gCalPresent;
+ }),
});