Google Meet - installable app (#5904)
* Abstract app category navigation * Send key schema to frontend Co-authored-by: Omar López <zomars@users.noreply.github.com> * Render keys for apps on admin * Add enabled col for apps * Save app keys to DB * Add checks for admin role * Abstract setup components * Add AdminAppsList to setup wizard * Migrate to v10 tRPC * Default hide keys * Display enabled apps * Merge branch 'main' into admin-apps-ui * Toggle calendars * WIP * Add params and include AppCategoryNavigation * Refactor getEnabledApps * Add warning for disabling apps * Fallback to cal video when a video app is disabled * WIP send disabled email * Send email to all users of event types with payment app * Disable Stripe when app is disabled * Disable apps in event types * Send email to users on disabled apps * Send email based on what app was disabled * WIP type fix * Disable navigation to apps list if already setup * UI import fixes * Waits for session data before redirecting * Updates admin seeded password To comply with admin password requirements * Update yarn.lock * Flex fixes * Adds admin middleware * Clean up * WIP * WIP * NTS * Add dirName to app metadata * Upsert app if not in db * Upsert app if not in db * Add dirName to app metadata * Add keys to app packages w/ keys * Merge with main * Toggle show keys & on enable * Fix empty keys * Fix lark calendar metadata * Fix some type errors * Fix Lark metadata & check for category when upserting * More type fixes * Fix types & add keys to google cal * WIP * WIP * WIP * More type fixes * Fix type errors * Fix type errors * More type fixes * More type fixes * More type fixes * Feedback * Fixes default value * Feedback * Migrate credential invalid col default value "false" * Upsert app on saving keys * Clean up * Validate app keys on frontend * Add nonempty to app keys schemas * Add web3 * Listlocale filter on categories / category * Grab app metadata via category or categories * Show empty screen if no apps are enabled * Fix type checks * Fix type checks * Fix type checks * Fix type checks * Fix type checks * Fix type checks * Replace .nonempty() w/ .min(1) * Fix type error * Address feedback * Draft Google Meet install button * Add install button and warning dialog * WIP * WIP * Display warning when Meet is selected * Display Google Meet warning on email to organizer * Fix email * Fix type errors * Fix type error * Add connected account component * Add warning message * Address comments * Address feedback * Clean up & add MeetLocationType * Use useApp hook * Translate to new API approach * Remove console.log * Refactor * Fix missing backup Cal video link * WIP * Address feedback * Update submodules * Feedback * Submodule sync Co-authored-by: Omar López <zomars@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: zomars <zomars@me.com> Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Peer Richelsen <peer@cal.com>
This commit is contained in:
parent
4e65c30e18
commit
75aef09338
|
@ -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 = (
|
|||
</li>
|
||||
);
|
||||
})}
|
||||
{validLocations.some((location) => location.type === MeetLocationType) && (
|
||||
<div className="flex text-sm text-gray-600">
|
||||
<Icon.FiCheck className="mt-0.5 mr-1.5 h-2 w-2.5" />
|
||||
<Trans i18nKey="event_type_requres_google_cal">
|
||||
<p>
|
||||
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
|
||||
Change it{" "}
|
||||
<Link
|
||||
href={`${CAL_URL}/event-types/${eventType.id}?tabName=advanced`}
|
||||
className="underline">
|
||||
here.
|
||||
</Link>{" "}
|
||||
We will fall back to Cal video if you do not change it.
|
||||
</p>
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
{validLocations.length > 0 && validLocations.length !== locationOptions.length && (
|
||||
<li>
|
||||
<Button StartIcon={Icon.FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}>
|
||||
|
@ -326,6 +345,7 @@ export const EventSetupTab = (
|
|||
<Skeleton as={Label} loadingClassName="w-16">
|
||||
{t("location")}
|
||||
</Skeleton>
|
||||
|
||||
<Controller
|
||||
name="locations"
|
||||
control={formMethods.control}
|
||||
|
|
|
@ -35,6 +35,24 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
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();
|
||||
|
|
|
@ -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 ({
|
||||
|
|
|
@ -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<typeof getStaticProps>) {
|
|||
images={source.data?.items as string[] | undefined}
|
||||
// tos="https://zoom.us/terms"
|
||||
// privacy="https://zoom.us/privacy"
|
||||
body={<div dangerouslySetInnerHTML={{ __html: md.render(source.content) }} />}
|
||||
body={
|
||||
<>
|
||||
{data.slug === "google-meet" && <ExisitingGoogleCal />}
|
||||
<div dangerouslySetInnerHTML={{ __html: md.render(source.content) }} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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}`;
|
||||
|
|
|
@ -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")),
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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" })
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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" }) });
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { defaultHandler } from "@calcom/lib/server";
|
||||
|
||||
export default defaultHandler({
|
||||
GET: import("./_getAdd"),
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
export { default as add } from "./add";
|
|
@ -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 (
|
||||
<div className="rounded-md bg-blue-100 py-3 px-4 text-blue-900">
|
||||
<div className="flex items-start space-x-2.5">
|
||||
<div>
|
||||
<Icon.FiAlertCircle className="font-semibold" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">{t("requires_google_calendar")}</span>
|
||||
<div>
|
||||
<>
|
||||
{isLoading ? (
|
||||
<SkeletonText className="h-4 w-full" />
|
||||
) : hasGoogleCal ? (
|
||||
t("connected_google_calendar")
|
||||
) : (
|
||||
<Trans i18nKey="no_google_calendar">
|
||||
Please connect your Google Calendar account{" "}
|
||||
<Link href={`${CAL_URL}/apps/google-calendar`} className="font-semibold text-blue-900">
|
||||
here
|
||||
</Link>
|
||||
</Trans>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExistingGoogleCal;
|
|
@ -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,
|
||||
})}
|
||||
<WarningDialog open={showWarningDialog} onOpenChange={setShowWarningDialog} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<Dialog name="Account check" open={props.open} onOpenChange={props.onOpenChange}>
|
||||
<DialogContent
|
||||
type="creation"
|
||||
title={t("using_meet_requires_calendar")}
|
||||
description={googleCalendarPresent ? "" : t("continue_to_install_google_calendar")}>
|
||||
<DialogFooter>
|
||||
<>
|
||||
<DialogClose
|
||||
type="button"
|
||||
color="secondary"
|
||||
tabIndex={-1}
|
||||
onClick={() => {
|
||||
props.onOpenChange?.(false);
|
||||
}}>
|
||||
{t("cancel")}
|
||||
</DialogClose>
|
||||
|
||||
<Button type="button" onClick={() => mutation.mutate("")}>
|
||||
{googleCalendarPresent ? t("install_google_meet") : t("install_google_calendar")}
|
||||
</Button>
|
||||
</>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { default as InstallAppButton } from "./InstallAppButton";
|
|
@ -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
|
||||
|
|
|
@ -7,6 +7,7 @@ import { ButtonProps } from "@calcom/ui";
|
|||
|
||||
export type IntegrationOAuthCallbackState = {
|
||||
returnTo: string;
|
||||
installGoogleVideo?: boolean;
|
||||
};
|
||||
|
||||
export interface InstallAppButtonProps {
|
||||
|
|
|
@ -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<EventResult<Exclude<Event, AdditionalInformation>>> = [];
|
||||
|
||||
// If and only if event type is a dedicated meeting, create a dedicated video meeting.
|
||||
if (isDedicated) {
|
||||
const result = await this.createVideoEvent(evt);
|
||||
|
|
|
@ -16,6 +16,7 @@ export const AppsStatus = (props: { calEvent: CalendarEvent; t: TFunction }) =>
|
|||
<li key={status.type} style={{ fontWeight: 400 }}>
|
||||
{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 && (
|
||||
<ul style={{ fontSize: "14px" }}>
|
||||
{status.warnings.map((warning, i) => (
|
||||
|
@ -23,7 +24,6 @@ export const AppsStatus = (props: { calEvent: CalendarEvent; t: TFunction }) =>
|
|||
))}
|
||||
</ul>
|
||||
)}
|
||||
{status.failures >= 1 && `❌ ${status.failures > 1 ? `(x${status.failures})` : ""}`}
|
||||
{status.errors.length >= 1 && (
|
||||
<ul>
|
||||
{status.errors.map((error, i) => (
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}),
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user