From 77600f81687fb7575bf692a41b9e5e6cb6d0d955 Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Mon, 13 Feb 2023 22:55:32 +0000 Subject: [PATCH] Feat/custom default video (#6971) * Add queries to fetch metadata and update default * Add to zod schema * Change default rules on settings page * Update query * Initall override on create event type * Working default * Change schema to save locationLink too * Update default conferncing to include link * Rename tsx to ts. Fix metadata keys * WIP on refactoring appstore installed * Add this to appstore installed apps too * Display only on conferencing apps * Add i18n strings * Change order in dropdown menu so delete is bottom * Refactor query to not expose all of metadata * Fix mr type checker * Possible e2e fix * Refactor metadata to use ctx * Refactor back to query cell * Alias schema * Adding comment for more info * Remove constant for clarity * Update nextjs-bundle-analysis.yml --------- Co-authored-by: Alex van Andel Co-authored-by: zomars --- apps/web/pages/apps/installed/[category].tsx | 245 +++++++++++------- .../settings/my-account/conferencing.tsx | 140 ++++++---- apps/web/public/static/locales/en/common.json | 3 + .../components/AppSetDefaultLinkDialog.tsx | 79 ++++++ packages/prisma/zod-utils.ts | 6 + packages/trpc/server/routers/viewer.tsx | 30 ++- .../viewer/{eventTypes.tsx => eventTypes.ts} | 27 +- 7 files changed, 377 insertions(+), 153 deletions(-) create mode 100644 packages/features/apps/components/AppSetDefaultLinkDialog.tsx rename packages/trpc/server/routers/viewer/{eventTypes.tsx => eventTypes.ts} (94%) diff --git a/apps/web/pages/apps/installed/[category].tsx b/apps/web/pages/apps/installed/[category].tsx index 5de4cf4b4a..b52b00777d 100644 --- a/apps/web/pages/apps/installed/[category].tsx +++ b/apps/web/pages/apps/installed/[category].tsx @@ -1,10 +1,12 @@ import { useRouter } from "next/router"; -import { useReducer } from "react"; +import { useReducer, useState } from "react"; import z from "zod"; import { AppSettings } from "@calcom/app-store/_components/AppSettings"; import { InstallAppButton } from "@calcom/app-store/components"; +import { EventLocationType, getEventLocationTypeFromApp } from "@calcom/app-store/locations"; import { InstalledAppVariants } from "@calcom/app-store/utils"; +import { AppSetDefaultLinkDailog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog"; import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { RouterOutputs, trpc } from "@calcom/trpc/react"; @@ -22,6 +24,7 @@ import { Dropdown, DropdownMenuItem, DropdownItem, + showToast, } from "@calcom/ui"; import { FiBarChart, @@ -41,7 +44,7 @@ import AppListCard from "@components/AppListCard"; import { CalendarListContainer } from "@components/apps/CalendarListContainer"; import InstalledAppsLayout from "@components/apps/layouts/InstalledAppsLayout"; -function ConnectOrDisconnectIntegrationButton(props: { +function ConnectOrDisconnectIntegrationMenuItem(props: { credentialIds: number[]; type: App["type"]; isGlobal?: boolean; @@ -60,22 +63,15 @@ function ConnectOrDisconnectIntegrationButton(props: { if (credentialId || type === "stripe_payment" || isGlobal) { return ( - - - + } + /> + ); + } return ( <> - {data.items.length > 0 ? ( -
- - {t("add")} - + - -
- ) : ( - - {t(`connect_${variant || "other"}_apps`)} - - } - /> - )} + color="secondary" + StartIcon={FiPlus}> + {t("add")} + + } + /> + ); }} @@ -257,17 +308,19 @@ export default function InstalledApps() { return ( <> - {categoryList.includes(category) && ( - - )} - {category === "calendar" && } - {category === "other" && ( - - )} +
+ {categoryList.includes(category) && ( + + )} + {category === "calendar" && } + {category === "other" && ( + + )} +
{ return ( @@ -43,12 +42,13 @@ const ConferencingLayout = () => { const { t } = useLocale(); const utils = trpc.useContext(); - const { data: apps, isLoading } = trpc.viewer.integrations.useQuery( - { variant: "conferencing", onlyInstalled: true }, - { - suspense: true, - } - ); + const { data: defaultConferencingApp, isLoading: defaultConferencingAppLoading } = + trpc.viewer.getUsersDefaultConferencingApp.useQuery(); + + const { data: apps, isLoading } = trpc.viewer.integrations.useQuery({ + variant: "conferencing", + onlyInstalled: true, + }); const deleteAppMutation = trpc.viewer.deleteCredential.useMutation({ onSuccess: () => { showToast("Integration deleted successfully", "success"); @@ -61,10 +61,20 @@ const ConferencingLayout = () => { }, }); + const updateDefaultAppMutation = trpc.viewer.updateUserDefaultConferencingApp.useMutation({ + onSuccess: () => { + showToast("Default app updated successfully", "success"); + utils.viewer.getUsersDefaultConferencingApp.invalidate(); + }, + }); + const [deleteAppModal, setDeleteAppModal] = useState(false); + const [locationType, setLocationType] = useState<(EventLocationType & { slug: string }) | undefined>( + undefined + ); const [deleteCredentialId, setDeleteCredentialId] = useState(0); - if (isLoading) + if (isLoading || defaultConferencingAppLoading) return ; return ( @@ -74,39 +84,67 @@ const ConferencingLayout = () => { {apps?.items && apps.items .map((app) => ({ ...app, title: app.title || app.name })) - .map((app) => ( - - - - + + + + + + + ); +} diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index 5813a8864a..fb9d2fea59 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -209,6 +209,12 @@ export const userMetadata = z vitalSettings: vitalSettingsUpdateSchema.optional(), isPremium: z.boolean().optional(), sessionTimeout: z.number().optional(), // Minutes + defaultConferencingApp: z + .object({ + appSlug: z.string().default("daily-video").optional(), + appLink: z.string().optional(), + }) + .optional(), }) .nullable(); diff --git a/packages/trpc/server/routers/viewer.tsx b/packages/trpc/server/routers/viewer.tsx index c71138daa9..8a664e26ec 100644 --- a/packages/trpc/server/routers/viewer.tsx +++ b/packages/trpc/server/routers/viewer.tsx @@ -472,7 +472,6 @@ const loggedInViewerRouter = router({ const { credentials } = user; const enabledApps = await getEnabledApps(credentials); - let apps = enabledApps.map( ({ credentials: _, credential: _1 /* don't leak to frontend */, ...app }) => { const credentialIds = credentials.filter((c) => c.type === app.type).map((c) => c.id); @@ -1181,6 +1180,35 @@ const loggedInViewerRouter = router({ }); } }), + getUsersDefaultConferencingApp: authedProcedure.query(async ({ ctx }) => { + return userMetadata.parse(ctx.user.metadata)?.defaultConferencingApp; + }), + updateUserDefaultConferencingApp: authedProcedure + .input( + z.object({ + appSlug: z.string().optional(), + appLink: z.string().optional(), + }) + ) + .mutation(async ({ ctx, input }) => { + const currentMetadata = userMetadata.parse(ctx.user.metadata); + + await ctx.prisma.user.update({ + where: { + id: ctx.user.id, + }, + data: { + metadata: { + ...currentMetadata, + defaultConferencingApp: { + appSlug: input.appSlug, + appLink: input.appLink, + }, + }, + }, + }); + return input; + }), }); export const viewerRouter = mergeRouters( diff --git a/packages/trpc/server/routers/viewer/eventTypes.tsx b/packages/trpc/server/routers/viewer/eventTypes.ts similarity index 94% rename from packages/trpc/server/routers/viewer/eventTypes.tsx rename to packages/trpc/server/routers/viewer/eventTypes.ts index 87f859b07b..e774886d71 100644 --- a/packages/trpc/server/routers/viewer/eventTypes.tsx +++ b/packages/trpc/server/routers/viewer/eventTypes.ts @@ -7,6 +7,7 @@ import { z } from "zod"; import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug"; import { DailyLocationType } from "@calcom/app-store/locations"; import { stripeDataSchema } from "@calcom/app-store/stripepayment/lib/server"; +import getApps from "@calcom/app-store/utils"; import { validateBookingLimitOrder } from "@calcom/lib"; import { CAL_URL } from "@calcom/lib/constants"; import getEventTypeById from "@calcom/lib/getEventTypeById"; @@ -17,6 +18,7 @@ import { CustomInputSchema, EventTypeMetaDataSchema, stringOrNumber, + userMetadata as userMetadataSchema, } from "@calcom/prisma/zod-utils"; import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype"; @@ -406,7 +408,29 @@ export const eventTypesRouter = router({ create: authedProcedure.input(createEventTypeInput).mutation(async ({ ctx, input }) => { const { schedulingType, teamId, ...rest } = input; const userId = ctx.user.id; + // Get Users default conferncing app + + const defaultConferencingData = userMetadataSchema.parse(ctx.user.metadata)?.defaultConferencingApp; const appKeys = await getAppKeysFromSlug("daily-video"); + + let locations: { type: string; link?: string }[] = []; + + // If no locations are passed in and the user has a daily api key then default to daily + if ( + (typeof rest?.locations === "undefined" || rest.locations?.length === 0) && + typeof appKeys.api_key === "string" + ) { + locations = [{ type: DailyLocationType }]; + } + + // If its defaulting to daily no point handling compute as its done + if (defaultConferencingData && defaultConferencingData.appSlug !== "daily-video") { + const credentials = ctx.user.credentials; + const foundApp = getApps(credentials).filter((app) => app.slug === defaultConferencingData.appSlug)[0]; // There is only one possible install here so index [0] is the one we are looking for ; + const locationType = foundApp?.locationOption?.value ?? DailyLocationType; // Default to Daily if no location type is found + locations = [{ type: locationType, link: defaultConferencingData.appLink }]; + } + const data: Prisma.EventTypeCreateInput = { ...rest, owner: teamId ? undefined : { connect: { id: userId } }, @@ -415,8 +439,7 @@ export const eventTypesRouter = router({ id: userId, }, }, - ...((typeof rest?.locations === "undefined" || rest.locations?.length === 0) && - typeof appKeys.api_key === "string" && { locations: [{ type: DailyLocationType }] }), + locations, }; if (teamId && schedulingType) {