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[],
},
});
};