feat: jelly seamless signup (#15220)
* feat: jelly seamless signup * fix: wrong import * add credentialId id to defaultLocation * add CredentialId and correct appType * fix: type error
This commit is contained in:
1 parent
889ff177c1
commit
33c1d49c52
8 files changed
+136
-35
No files matched your search
@@ -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 (
|
||||
<div className="relative flex-1 flex-col items-start justify-start px-4 md:flex md:px-8 lg:flex-row lg:px-0">
|
||||
|
||||
@@ -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;
|
||||
@@ -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));
|
||||
|
||||
@@ -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" }));
|
||||
}
|
||||
Vendored
+1
@@ -12,6 +12,7 @@ export type IntegrationOAuthCallbackState = {
|
||||
installGoogleVideo?: boolean;
|
||||
appOnboardingRedirectUrl?: string;
|
||||
teamId?: number;
|
||||
defaultInstall?: boolean;
|
||||
};
|
||||
|
||||
export type CredentialOwner = {
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
};
|
||||
+13
-1
@@ -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[],
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in new issue
Block a user