diff --git a/apps/web/components/apps/AppPage.tsx b/apps/web/components/apps/AppPage.tsx index ae2313c733..ea34189836 100644 --- a/apps/web/components/apps/AppPage.tsx +++ b/apps/web/components/apps/AppPage.tsx @@ -7,6 +7,7 @@ import { AppDependencyComponent, InstallAppButton } from "@calcom/app-store/comp import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration"; import classNames from "@calcom/lib/classNames"; import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import type { App as AppType } from "@calcom/types/App"; @@ -72,6 +73,8 @@ export const AppPage = ({ dirName, }: AppPageProps) => { const { t, i18n } = useLocale(); + const searchParams = useCompatSearchParams(); + const hasDescriptionItems = descriptionItems && descriptionItems.length > 0; const mutation = useAddAppMutation(null, { @@ -120,6 +123,11 @@ export const AppPage = ({ // variant not other allows, an app to be shown in calendar category without requiring an actual calendar connection e.g. vimcal // Such apps, can only be installed once. const allowedMultipleInstalls = categories.indexOf("calendar") > -1 && variant !== "other"; + useEffect(() => { + if (searchParams?.get("defaultInstall") === "true") { + mutation.mutate({ type, variant, slug, defaultInstall: true }); + } + }, []); return (
diff --git a/packages/app-store/_utils/setDefaultConferencingApp.ts b/packages/app-store/_utils/setDefaultConferencingApp.ts new file mode 100644 index 0000000000..555e2949a9 --- /dev/null +++ b/packages/app-store/_utils/setDefaultConferencingApp.ts @@ -0,0 +1,57 @@ +import type { LocationObject } from "@calcom/app-store/locations"; +import { getAppFromSlug } from "@calcom/app-store/utils"; +import getBulkEventTypes from "@calcom/lib/event-types/getBulkEventTypes"; +import prisma from "@calcom/prisma"; +import { userMetadata } from "@calcom/prisma/zod-utils"; + +const setDefaultConferencingApp = async (userId: number, appSlug: string) => { + const eventTypes = await getBulkEventTypes(userId); + const eventTypeIds = eventTypes.eventTypes.map((item) => item.id); + const foundApp = getAppFromSlug(appSlug); + const appType = foundApp?.appData?.location?.type; + + if (!appType) { + return; + } + + const user = await prisma.user.findUnique({ + where: { + id: userId, + }, + select: { + metadata: true, + credentials: true, + }, + }); + + const currentMetadata = userMetadata.parse(user?.metadata); + const credentialId = user?.credentials.find((item) => item.appId == appSlug)?.id; + + //Update the default conferencing app for the user. + await prisma.user.update({ + where: { + id: userId, + }, + data: { + metadata: { + ...currentMetadata, + defaultConferencingApp: { + appSlug, + }, + }, + }, + }); + + await prisma.eventType.updateMany({ + where: { + id: { + in: eventTypeIds, + }, + userId, + }, + data: { + locations: [{ type: appType, credentialId }] as LocationObject[], + }, + }); +}; +export default setDefaultConferencingApp; diff --git a/packages/app-store/_utils/useAddAppMutation.ts b/packages/app-store/_utils/useAddAppMutation.ts index 6530e9516e..1d61f289d3 100644 --- a/packages/app-store/_utils/useAddAppMutation.ts +++ b/packages/app-store/_utils/useAddAppMutation.ts @@ -44,13 +44,22 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta const mutation = useMutation< AddAppMutationData, Error, - { type?: App["type"]; variant?: string; slug?: string; isOmniInstall?: boolean; teamId?: number } | "" + | { + type?: App["type"]; + variant?: string; + slug?: string; + isOmniInstall?: boolean; + teamId?: number; + defaultInstall?: boolean; + } + | "" >({ ...options, mutationFn: async (variables) => { let type: string | null | undefined; let isOmniInstall; const teamId = variables && variables.teamId ? variables.teamId : undefined; + const defaultInstall = variables && variables.defaultInstall ? variables.defaultInstall : undefined; if (variables === "") { type = _type; } else { @@ -76,6 +85,7 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta fromApp: true, ...(type === "google_calendar" && { installGoogleVideo: options?.installGoogleVideo }), ...(teamId && { teamId }), + ...(defaultInstall && { defaultInstall }), }; const stateStr = encodeURIComponent(JSON.stringify(state)); diff --git a/packages/app-store/jelly/api/callback.ts b/packages/app-store/jelly/api/callback.ts index 3ba6264286..714173cde4 100644 --- a/packages/app-store/jelly/api/callback.ts +++ b/packages/app-store/jelly/api/callback.ts @@ -5,8 +5,11 @@ import prisma from "@calcom/prisma"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; +import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; +import setDefaultConferencingApp from "../../_utils/setDefaultConferencingApp"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const state = decodeOAuthState(req); const userId = req.session?.user.id; if (!userId) { return res.status(404).json({ message: "No user found" }); @@ -53,6 +56,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) { access_token: responseBody.access_token }, req ); + if (state?.defaultInstall) { + setDefaultConferencingApp(userId, "jelly"); + } res.redirect(getInstalledAppPath({ variant: "conferencing", slug: "jelly" })); } diff --git a/packages/app-store/types.d.ts b/packages/app-store/types.d.ts index da1fb01df9..22a6101dc7 100644 --- a/packages/app-store/types.d.ts +++ b/packages/app-store/types.d.ts @@ -12,6 +12,7 @@ export type IntegrationOAuthCallbackState = { installGoogleVideo?: boolean; appOnboardingRedirectUrl?: string; teamId?: number; + defaultInstall?: boolean; }; export type CredentialOwner = { diff --git a/packages/lib/event-types/getBulkEventTypes.ts b/packages/lib/event-types/getBulkEventTypes.ts new file mode 100644 index 0000000000..f150a07a87 --- /dev/null +++ b/packages/lib/event-types/getBulkEventTypes.ts @@ -0,0 +1,38 @@ +import { getAppFromLocationValue } from "@calcom/app-store/utils"; +import { prisma } from "@calcom/prisma"; +import { eventTypeLocations as eventTypeLocationsSchema } from "@calcom/prisma/zod-utils"; + +const getBulkEventTypes = async (userId: number) => { + const eventTypes = await prisma.eventType.findMany({ + where: { + userId, + team: null, + }, + select: { + id: true, + title: true, + locations: true, + }, + }); + + const eventTypesWithLogo = eventTypes.map((eventType) => { + const locationParsed = eventTypeLocationsSchema.safeParse(eventType.locations); + + // some events has null as location for legacy reasons, so this fallbacks to daily video + const app = getAppFromLocationValue( + locationParsed.success && locationParsed.data?.[0]?.type + ? locationParsed.data[0].type + : "integrations:daily" + ); + return { + ...eventType, + logo: app?.logo, + }; + }); + + return { + eventTypes: eventTypesWithLogo, + }; +}; + +export default getBulkEventTypes; diff --git a/packages/trpc/server/routers/viewer/eventTypes/bulkEventFetch.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/bulkEventFetch.handler.ts index 107581f947..e113f3faea 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/bulkEventFetch.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/bulkEventFetch.handler.ts @@ -1,6 +1,4 @@ -import { getAppFromLocationValue } from "@calcom/app-store/utils"; -import { prisma } from "@calcom/prisma"; -import { eventTypeLocations as eventTypeLocationsSchema } from "@calcom/prisma/zod-utils"; +import getBulkEventTypes from "@calcom/lib/event-types/getBulkEventTypes"; import type { TrpcSessionUser } from "../../../trpc"; @@ -11,34 +9,5 @@ type BulkEventFetchOptions = { }; export const bulkEventFetchHandler = async ({ ctx }: BulkEventFetchOptions) => { - const eventTypes = await prisma.eventType.findMany({ - where: { - userId: ctx.user.id, - team: null, - }, - select: { - id: true, - title: true, - locations: true, - }, - }); - - const eventTypesWithLogo = eventTypes.map((eventType) => { - const locationParsed = eventTypeLocationsSchema.safeParse(eventType.locations); - - // some events has null as location for legacy reasons, so this fallbacks to daily video - const app = getAppFromLocationValue( - locationParsed.success && locationParsed.data?.[0]?.type - ? locationParsed.data[0].type - : "integrations:daily" - ); - return { - ...eventType, - logo: app?.logo, - }; - }); - - return { - eventTypes: eventTypesWithLogo, - }; + return getBulkEventTypes(ctx.user.id); }; diff --git a/packages/trpc/server/routers/viewer/eventTypes/bulkUpdateToDefaultLocation.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/bulkUpdateToDefaultLocation.handler.ts index a153720721..d960138506 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/bulkUpdateToDefaultLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/bulkUpdateToDefaultLocation.handler.ts @@ -38,6 +38,16 @@ export const bulkUpdateToDefaultLocationHandler = async ({ }); } + const credential = await prisma.credential.findFirst({ + where: { + userId: ctx.user.id, + appId: foundApp.slug, + }, + select: { + id: true, + }, + }); + return await prisma.eventType.updateMany({ where: { id: { @@ -46,7 +56,9 @@ export const bulkUpdateToDefaultLocationHandler = async ({ userId: ctx.user.id, }, data: { - locations: [{ type: appType, link: defaultApp.appLink }] as LocationObject[], + locations: [ + { type: appType, link: defaultApp.appLink, credentialId: credential?.id }, + ] as LocationObject[], }, }); };