diff --git a/packages/app-store/server.ts b/packages/app-store/server.ts index 519097ddec..ead279afe2 100644 --- a/packages/app-store/server.ts +++ b/packages/app-store/server.ts @@ -1,6 +1,7 @@ import type { TFunction } from "next-i18next"; import type { CredentialDataWithTeamName } from "@calcom/app-store/utils"; +import { defaultVideoAppCategories } from "@calcom/app-store/utils"; import getEnabledApps from "@calcom/lib/apps/getEnabledApps"; import { prisma } from "@calcom/prisma"; import { AppCategories } from "@calcom/prisma/enums"; @@ -56,7 +57,7 @@ export async function getLocationGroupedOptions( ...idToSearchObject, app: { categories: { - hasSome: [AppCategories.conferencing, AppCategories.video], + hasSome: defaultVideoAppCategories, }, }, }, @@ -79,26 +80,24 @@ export async function getLocationGroupedOptions( const integrations = await getEnabledApps(credentials, true); integrations.forEach((app) => { + // All apps that are labeled as a locationOption are video apps. if (app.locationOption) { // All apps that are labeled as a locationOption are video apps. Extract the secondary category if available - let category = + let groupByCategory = app.categories.length >= 2 - ? app.categories.find( - (category) => - !([AppCategories.video, AppCategories.conferencing] as string[]).includes(category) - ) - : app.category; - if (!category) category = AppCategories.conferencing; + ? app.categories.find((groupByCategory) => !defaultVideoAppCategories.includes(groupByCategory)) + : app.categories[0] || app.category; + if (!groupByCategory) groupByCategory = AppCategories.conferencing; for (const credential of app.credentials) { const label = `${app.locationOption.label} ${ credential.team?.name ? `(${credential.team.name})` : "" }`; const option = { ...app.locationOption, label, icon: app.logo, slug: app.slug, credential }; - if (apps[category]) { - apps[category] = [...apps[category], option]; + if (apps[groupByCategory]) { + apps[groupByCategory] = [...apps[groupByCategory], option]; } else { - apps[category] = [option]; + apps[groupByCategory] = [option]; } } } diff --git a/packages/app-store/utils.ts b/packages/app-store/utils.ts index 4bfd0da129..81c0e07b5c 100644 --- a/packages/app-store/utils.ts +++ b/packages/app-store/utils.ts @@ -1,3 +1,4 @@ +import type { AppCategories } from "@prisma/client"; import { Prisma } from "@prisma/client"; // If you import this file on any app it should produce circular dependency @@ -143,8 +144,17 @@ export function doesAppSupportTeamInstall( concurrentMeetings: boolean | undefined = undefined ) { return !appCategories.some( - (category) => category === "calendar" || (category === "conferencing" && !concurrentMeetings) + (category) => + category === "calendar" || + (defaultVideoAppCategories.includes(category as AppCategories) && !concurrentMeetings) ); } +export const defaultVideoAppCategories: AppCategories[] = [ + "conferencing", + "messaging", + // Legacy name for conferencing + "video", +]; + export default getApps; diff --git a/packages/types/App.d.ts b/packages/types/App.d.ts index 520f7a87b2..195b2aa4c9 100644 --- a/packages/types/App.d.ts +++ b/packages/types/App.d.ts @@ -1,4 +1,4 @@ -import type { Prisma } from "@prisma/client"; +import type { AppCategories, Prisma } from "@prisma/client"; import type { Tag } from "@calcom/app-store/types"; @@ -31,6 +31,10 @@ type DynamicLinkBasedEventLocation = { export type EventLocationTypeFromAppMeta = StaticLinkBasedEventLocation | DynamicLinkBasedEventLocation; type AppData = { + /** + * TODO: We must assert that if `location` is set in App config.json, then it must have atleast Messaging or Conferencing as a category. + * This is because we fetch only those credentials(as an optimization) which match that category. + */ location?: EventLocationTypeFromAppMeta; tag?: Tag; } | null; @@ -81,14 +85,17 @@ export interface App { /** The slug for the app store public page inside `/apps/[slug] */ slug: string; - /** The category to which this app belongs, currently we have `calendar`, `payment` or `video` */ + /** The category to which this app belongs. Remove all usages of category and then remove the prop */ /* * @deprecated Use categories */ category?: string; - /** The category to which this app belongs, currently we have `calendar`, `payment` or `video` */ - categories: string[]; + /** The category to which this app belongs. */ + /** + * Messaging and Conferencing(Earlier called Video) are considered location apps and are fetched when configuring an event-type location. + */ + categories: AppCategories[]; /** * `User` is the broadest category. `EventType` is when you want to add features to EventTypes. * See https://app.gitbook.com/o/6snd8PyPYMhg0wUw6CeQ/s/VXRprBTuMlihk37NQgUU/~/changes/6xkqZ4qvJ3Xh9k8UaWaZ/engineering/product-specs/app-store#user-apps for more details diff --git a/packages/ui/components/apps/AllApps.tsx b/packages/ui/components/apps/AllApps.tsx index f2710f77e6..ce3b821913 100644 --- a/packages/ui/components/apps/AllApps.tsx +++ b/packages/ui/components/apps/AllApps.tsx @@ -1,4 +1,5 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; +import type { AppCategories } from "@prisma/client"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import type { UIEvent } from "react"; import { useEffect, useRef, useState } from "react"; @@ -156,7 +157,7 @@ export function AllApps({ apps, searchText, categories, userAdminTeams }: AllApp .filter((app) => selectedCategory !== null ? app.categories - ? app.categories.includes(selectedCategory) + ? app.categories.includes(selectedCategory as AppCategories) : app.category === selectedCategory : true )