refactor: conferencing apps atoms web wrapper (#17732)

* conferencing apps atoms web wrapper

* Update DisconnectIntegrationModal.tsx

* formating changes

* handleBulkUpdateDefaultLocation -> bulkUpdateFunction

* fix: type error for handleRemoveApp

* fix: type error for handleRemoveApp

* refactor: removed ConferencingView component and migrated its functionality to ConferencingAppsViewWebWrapper

* refactor: moved AppList, AppListCard to packages/features

* fix: type changes

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
Somay Chauhan
2024-11-25 09:53:38 +02:00
committed by GitHub
co-authored by Morgan
parent 8b9693ea60
commit 868cf91c35
16 changed files with 354 additions and 186 deletions
@@ -1,11 +1,8 @@
import { _generateMetadata } from "app/_utils";
import { getFixedT } from "app/_utils";
import { ConferencingAppsViewWebWrapper } from "@calcom/atoms/monorepo";
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { Button } from "@calcom/ui";
import ConferencingView from "~/settings/my-account/conferencing-view";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -17,22 +14,12 @@ const Page = async () => {
const session = await getServerSessionForAppDir();
const t = await getFixedT(session?.user.locale || "en");
const AddConferencingButton = () => {
return (
<Button color="secondary" StartIcon="plus" href="/apps/categories/conferencing">
{t("add")}
</Button>
);
};
return (
<SettingsHeader
<ConferencingAppsViewWebWrapper
title={t("conferencing")}
description={t("conferencing_description")}
CTA={<AddConferencingButton />}
borderInShellHeader={true}>
<ConferencingView />
</SettingsHeader>
add={t("add")}
/>
);
};
@@ -7,6 +7,7 @@ import {
SelectedCalendarsSettingsWebWrapper,
DestinationCalendarSettingsWebWrapper,
} from "@calcom/atoms/monorepo";
import AppListCard from "@calcom/features/apps/components/AppListCard";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
@@ -21,7 +22,6 @@ import {
import { QueryCell } from "@lib/QueryCell";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import AppListCard from "@components/AppListCard";
import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingTitleWithConnections";
type Props = {
@@ -3,19 +3,28 @@
import { useReducer } from "react";
import getAppCategoryTitle from "@calcom/app-store/_utils/getAppCategoryTitle";
import type { UpdateDefaultConferencingAppParams } from "@calcom/features/apps/components/AppList";
import { AppList } from "@calcom/features/apps/components/AppList";
import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal";
import type { RemoveAppParams } from "@calcom/features/apps/components/DisconnectIntegrationModal";
import type { BulkUpdatParams } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { AppCategories } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import type { Icon } from "@calcom/ui";
import { AppSkeletonLoader as SkeletonLoader, Button, EmptyScreen, ShellSubHeading } from "@calcom/ui";
import {
AppSkeletonLoader as SkeletonLoader,
Button,
EmptyScreen,
ShellSubHeading,
showToast,
} from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import type { querySchemaType, getServerSideProps } from "@lib/apps/installed/[category]/getServerSideProps";
import { AppList } from "@components/apps/AppList";
import { CalendarListContainer } from "@components/apps/CalendarListContainer";
import InstalledAppsLayout from "@components/apps/layouts/InstalledAppsLayout";
@@ -31,6 +40,7 @@ const IntegrationsContainer = ({
handleDisconnect,
}: IntegrationsContainerProps): JSX.Element => {
const { t } = useLocale();
const utils = trpc.useUtils();
const query = trpc.viewer.integrations.useQuery({
variant,
exclude,
@@ -38,6 +48,42 @@ const IntegrationsContainer = ({
includeTeamInstalledApps: true,
});
const { data: defaultConferencingApp } = trpc.viewer.getUsersDefaultConferencingApp.useQuery();
const updateDefaultAppMutation = trpc.viewer.updateUserDefaultConferencingApp.useMutation();
const updateLocationsMutation = trpc.viewer.eventTypes.bulkUpdateToDefaultLocation.useMutation();
const handleUpdateDefaultConferencingApp = ({ appSlug, callback }: UpdateDefaultConferencingAppParams) => {
updateDefaultAppMutation.mutate(
{ appSlug },
{
onSuccess: () => {
showToast("Default app updated successfully", "success");
utils.viewer.getUsersDefaultConferencingApp.invalidate();
callback();
},
onError: (error) => {
showToast(`Error: ${error.message}`, "error");
},
}
);
};
const handleBulkUpdateDefaultLocation = ({ eventTypeIds, callback }: BulkUpdatParams) => {
updateLocationsMutation.mutate(
{
eventTypeIds,
},
{
onSuccess: () => {
utils.viewer.getUsersDefaultConferencingApp.invalidate();
callback();
},
}
);
};
// TODO: Refactor and reuse getAppCategories?
const emptyIcon: Record<AppCategories, React.ComponentProps<typeof Icon>["name"]> = {
calendar: "calendar",
@@ -95,7 +141,15 @@ const IntegrationsContainer = ({
}
/>
<AppList handleDisconnect={handleDisconnect} data={data} variant={variant} />
<AppList
handleDisconnect={handleDisconnect}
data={data}
variant={variant}
defaultConferencingApp={defaultConferencingApp}
handleUpdateDefaultConferencingApp={handleUpdateDefaultConferencingApp}
handleBulkUpdateDefaultLocation={handleBulkUpdateDefaultLocation}
isBulkUpdateDefaultLocationPending={updateDefaultAppMutation.isPending}
/>
</div>
);
}}
@@ -114,6 +168,7 @@ export type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function InstalledApps(props: PageProps) {
const searchParams = useCompatSearchParams();
const { t } = useLocale();
const utils = trpc.useUtils();
const category = searchParams?.get("category") as querySchemaType["category"];
const categoryList: AppCategories[] = Object.values(AppCategories).filter((category) => {
// Exclude calendar and other from categoryList, we handle those slightly differently below
@@ -136,6 +191,26 @@ export default function InstalledApps(props: PageProps) {
updateData({ isOpen: true, credentialId, teamId });
};
const deleteCredentialMutation = trpc.viewer.deleteCredential.useMutation();
const handleRemoveApp = ({ credentialId, teamId, callback }: RemoveAppParams) => {
deleteCredentialMutation.mutate(
{ id: credentialId, teamId },
{
onSuccess: () => {
showToast(t("app_removed_successfully"), "success");
callback();
utils.viewer.integrations.invalidate();
utils.viewer.connectedCalendars.invalidate();
},
onError: () => {
showToast(t("error_removing_app"), "error");
callback();
},
}
);
};
return (
<>
<InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}>
@@ -156,6 +231,7 @@ export default function InstalledApps(props: PageProps) {
isOpen={data.isOpen}
credentialId={data.credentialId}
teamId={data.teamId}
handleRemoveApp={handleRemoveApp}
/>
</>
);
@@ -4,6 +4,7 @@ import { useRouter } from "next/navigation";
import { useState } from "react";
import { AvailabilitySettings } from "@calcom/atoms/monorepo";
import type { BulkUpdatParams } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
import { withErrorFromUnknown } from "@calcom/lib/getClientErrorFromUnknown";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -47,13 +48,22 @@ export const AvailabilitySettingsWebWrapper = ({
const [isBulkUpdateModalOpen, setIsBulkUpdateModalOpen] = useState(false);
const bulkUpdateDefaultAvailabilityMutation =
trpc.viewer.availability.schedule.bulkUpdateToDefaultAvailability.useMutation({
onSuccess: () => {
utils.viewer.availability.list.invalidate();
setIsBulkUpdateModalOpen(false);
showToast(t("success"), "success");
trpc.viewer.availability.schedule.bulkUpdateToDefaultAvailability.useMutation();
const bulkUpdateFunction = ({ eventTypeIds, callback }: BulkUpdatParams) => {
bulkUpdateDefaultAvailabilityMutation.mutate(
{
eventTypeIds,
},
});
{
onSuccess: () => {
utils.viewer.availability.list.invalidate();
callback();
showToast(t("success"), "success");
},
}
);
};
const isDefaultSchedule = me.data?.defaultScheduleId === scheduleId;
@@ -130,7 +140,7 @@ export const AvailabilitySettingsWebWrapper = ({
bulkUpdateModalProps={{
isOpen: isBulkUpdateModalOpen,
setIsOpen: setIsBulkUpdateModalOpen,
save: bulkUpdateDefaultAvailabilityMutation.mutate,
save: bulkUpdateFunction,
isSaving: bulkUpdateDefaultAvailabilityMutation.isPending,
}}
/>
@@ -7,6 +7,7 @@ import { useCallback, useState } from "react";
import SkeletonLoader from "@calcom/features/availability/components/SkeletonLoader";
import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
import type { BulkUpdatParams } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
import Shell from "@calcom/features/shell/Shell";
import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/components/AvailabilitySliderTable";
@@ -80,13 +81,22 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
});
const bulkUpdateDefaultAvailabilityMutation =
trpc.viewer.availability.schedule.bulkUpdateToDefaultAvailability.useMutation({
onSuccess: () => {
utils.viewer.availability.list.invalidate();
setBulkUpdateModal(false);
showToast(t("success"), "success");
trpc.viewer.availability.schedule.bulkUpdateToDefaultAvailability.useMutation();
const bulkUpdateFunction = ({ eventTypeIds, callback }: BulkUpdatParams) => {
bulkUpdateDefaultAvailabilityMutation.mutate(
{
eventTypeIds,
},
});
{
onSuccess: () => {
utils.viewer.availability.list.invalidate();
showToast(t("success"), "success");
callback();
},
}
);
};
const duplicateMutation = trpc.viewer.availability.schedule.duplicate.useMutation({
onSuccess: async ({ schedule }) => {
@@ -149,7 +159,7 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
isPending={bulkUpdateDefaultAvailabilityMutation.isPending}
open={bulkUpdateModal}
setOpen={setBulkUpdateModal}
bulkUpdateFunction={bulkUpdateDefaultAvailabilityMutation.mutate}
bulkUpdateFunction={bulkUpdateFunction}
description={t("default_schedules_bulk_description")}
/>
)}
@@ -1,100 +0,0 @@
"use client";
import { useReducer } from "react";
import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, EmptyScreen, SkeletonContainer, SkeletonText } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import { AppList } from "@components/apps/AppList";
const SkeletonLoader = () => {
return (
<SkeletonContainer>
<div className="divide-subtle border-subtle space-y-6 rounded-b-lg border border-t-0 px-6 py-4">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
</div>
</SkeletonContainer>
);
};
type ModalState = {
isOpen: boolean;
credentialId: null | number;
};
const ConferencingView = () => {
const { t } = useLocale();
const [modal, updateModal] = useReducer(
(data: ModalState, partialData: Partial<ModalState>) => ({ ...data, ...partialData }),
{
isOpen: false,
credentialId: null,
}
);
const query = trpc.viewer.integrations.useQuery({
variant: "conferencing",
onlyInstalled: true,
});
const handleModelClose = () => {
updateModal({ isOpen: false, credentialId: null });
};
const handleDisconnect = (credentialId: number) => {
updateModal({ isOpen: true, credentialId });
};
return (
<>
<div className="bg-default w-full sm:mx-0 xl:mt-0">
<QueryCell
query={query}
customLoader={<SkeletonLoader />}
success={({ data }) => {
if (!data.items.length) {
return (
<EmptyScreen
Icon="calendar"
headline={t("no_category_apps", {
category: t("conferencing").toLowerCase(),
})}
description={t("no_category_apps_description_conferencing")}
buttonRaw={
<Button
color="secondary"
data-testid="connect-conferencing-apps"
href="/apps/categories/conferencing">
{t("connect_conference_apps")}
</Button>
}
/>
);
}
return (
<AppList
listClassName="rounded-lg rounded-t-none border-t-0 max-w-full"
handleDisconnect={handleDisconnect}
data={data}
variant="conferencing"
/>
);
}}
/>
</div>
<DisconnectIntegrationModal
handleModelClose={handleModelClose}
isOpen={modal.isOpen}
credentialId={modal.credentialId}
/>
</>
);
};
export default ConferencingView;
+1 -2
View File
@@ -10,6 +10,7 @@ import { z } from "zod";
import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavigation";
import { appKeysSchemas } from "@calcom/app-store/apps.keys-schemas.generated";
import AppListCard from "@calcom/features/apps/components/AppListCard";
import { classNames as cs } from "@calcom/lib";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -35,8 +36,6 @@ import {
TextField,
} from "@calcom/ui";
import AppListCard from "../../../apps/web/components/AppListCard";
type App = RouterOutputs["viewer"]["appsRouter"]["listLocal"][number];
const IntegrationContainer = ({
@@ -4,7 +4,9 @@ import { AppSettings } from "@calcom/app-store/_components/AppSettings";
import { InstallAppButton } from "@calcom/app-store/components";
import { getLocationFromApp, type EventLocationType } from "@calcom/app-store/locations";
import type { CredentialOwner } from "@calcom/app-store/types";
import AppListCard from "@calcom/features/apps/components/AppListCard";
import { AppSetDefaultLinkDialog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
import type { BulkUpdatParams } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { AppCategories } from "@calcom/prisma/enums";
@@ -22,18 +24,29 @@ import {
showToast,
} from "@calcom/ui";
import AppListCard from "@components/AppListCard";
export type UpdateDefaultConferencingAppParams = { appSlug: string; callback: () => void };
interface AppListProps {
variant?: AppCategories;
data: RouterOutputs["viewer"]["integrations"];
handleDisconnect: (credentialId: number) => void;
listClassName?: string;
defaultConferencingApp: RouterOutputs["viewer"]["getUsersDefaultConferencingApp"];
handleUpdateDefaultConferencingApp: (params: UpdateDefaultConferencingAppParams) => void;
handleBulkUpdateDefaultLocation: (params: BulkUpdatParams) => void;
isBulkUpdateDefaultLocationPending: boolean;
}
export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppListProps) => {
const { data: defaultConferencingApp } = trpc.viewer.getUsersDefaultConferencingApp.useQuery();
const utils = trpc.useUtils();
export const AppList = ({
data,
handleDisconnect,
variant,
listClassName,
defaultConferencingApp,
handleUpdateDefaultConferencingApp,
handleBulkUpdateDefaultLocation,
isBulkUpdateDefaultLocationPending,
}: AppListProps) => {
const [bulkUpdateModal, setBulkUpdateModal] = useState(false);
const [locationType, setLocationType] = useState<(EventLocationType & { slug: string }) | undefined>(
undefined
@@ -44,17 +57,6 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
showToast("Default app updated successfully", "success");
}, []);
const updateDefaultAppMutation = trpc.viewer.updateUserDefaultConferencingApp.useMutation({
onSuccess: () => {
showToast("Default app updated successfully", "success");
utils.viewer.getUsersDefaultConferencingApp.invalidate();
setBulkUpdateModal(true);
},
onError: (error) => {
showToast(`Error: ${error.message}`, "error");
},
});
const ChildAppCard = ({
item,
}: {
@@ -96,8 +98,9 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
if (locationType?.linkType === "static") {
setLocationType({ ...locationType, slug: appSlug });
} else {
updateDefaultAppMutation.mutate({
handleUpdateDefaultConferencingApp({
appSlug,
callback: () => setBulkUpdateModal(true),
});
}
}}>
@@ -153,12 +156,6 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
});
const { t } = useLocale();
const updateLocationsMutation = trpc.viewer.eventTypes.bulkUpdateToDefaultLocation.useMutation({
onSuccess: () => {
utils.viewer.getUsersDefaultConferencingApp.invalidate();
setBulkUpdateModal(false);
},
});
return (
<>
<List className={listClassName}>
@@ -179,10 +176,10 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
{bulkUpdateModal && (
<BulkEditDefaultForEventsModal
bulkUpdateFunction={updateLocationsMutation.mutate}
bulkUpdateFunction={handleBulkUpdateDefaultLocation}
open={bulkUpdateModal}
setOpen={setBulkUpdateModal}
isPending={updateLocationsMutation.isPending}
isPending={isBulkUpdateDefaultLocationPending}
description={t("default_conferencing_bulk_description")}
/>
)}
@@ -6,6 +6,7 @@ import { useEffect, useRef, useState } from "react";
import { z } from "zod";
import type { CredentialOwner } from "@calcom/app-store/types";
import { useIsPlatform } from "@calcom/atoms/monorepo";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { AppListCard as AppListCardComponent } from "@calcom/ui";
@@ -35,7 +36,10 @@ export type AppListCardProps = {
const schema = z.object({ hl: z.string().optional() });
export default function AppListCard(props: AppListCardProps) {
function AppListCardPlatformWrapper(props: AppListCardProps) {
return <AppListCardComponent {...props} />;
}
function AppListCardWebWrapper(props: AppListCardProps) {
const { slug, shouldHighlight } = props;
const {
data: { hl },
@@ -70,3 +74,8 @@ export default function AppListCard(props: AppListCardProps) {
return <AppListCardComponent {...props} />;
}
export default function AppListCard(props: AppListCardProps) {
const isPlatform = useIsPlatform();
return isPlatform ? <AppListCardPlatformWrapper {...props} /> : <AppListCardWebWrapper {...props} />;
}
@@ -1,12 +1,13 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Dialog, showToast, ConfirmationDialogContent } from "@calcom/ui";
import { Dialog, ConfirmationDialogContent } from "@calcom/ui";
export type RemoveAppParams = { credentialId: number; teamId?: number; callback: () => void };
interface DisconnectIntegrationModalProps {
credentialId: number | null;
isOpen: boolean;
handleModelClose: () => void;
teamId?: number;
handleRemoveApp: (params: RemoveAppParams) => void;
}
export default function DisconnectIntegrationModal({
@@ -14,23 +15,9 @@ export default function DisconnectIntegrationModal({
isOpen,
handleModelClose,
teamId,
handleRemoveApp,
}: DisconnectIntegrationModalProps) {
const { t } = useLocale();
const utils = trpc.useUtils();
const mutation = trpc.viewer.deleteCredential.useMutation({
onSuccess: () => {
showToast(t("app_removed_successfully"), "success");
handleModelClose();
utils.viewer.integrations.invalidate();
utils.viewer.connectedCalendars.invalidate();
},
onError: () => {
showToast(t("error_removing_app"), "error");
handleModelClose();
},
});
return (
<Dialog open={isOpen} onOpenChange={handleModelClose}>
<ConfirmationDialogContent
@@ -39,7 +26,7 @@ export default function DisconnectIntegrationModal({
confirmBtnText={t("yes_remove_app")}
onConfirm={() => {
if (credentialId) {
mutation.mutate({ id: credentialId, teamId });
handleRemoveApp({ credentialId, teamId, callback: () => handleModelClose() });
}
}}>
<p className="mt-5">{t("are_you_sure_you_want_to_remove_this_app")}</p>
@@ -10,10 +10,12 @@ export const BulkUpdateEventSchema = z.object({
eventTypeIds: z.array(z.number()),
});
export type BulkUpdatParams = { eventTypeIds: number[]; callback: () => void };
export function BulkEditDefaultForEventsModal(props: {
open: boolean;
setOpen: (open: boolean) => void;
bulkUpdateFunction: ({ eventTypeIds }: { eventTypeIds: number[] }) => void;
bulkUpdateFunction: (params: BulkUpdatParams) => void;
isPending: boolean;
description: string;
}) {
@@ -42,7 +44,10 @@ export function BulkEditDefaultForEventsModal(props: {
<Form
form={form}
handleSubmit={(values) => {
props.bulkUpdateFunction(values);
props.bulkUpdateFunction({
eventTypeIds: values.eventTypeIds,
callback: () => props.setOpen(false),
});
}}>
<div className="flex flex-col space-y-2">
{data.eventTypes.length > 0 && (
@@ -5,6 +5,7 @@ import { useMemo, useState } from "react";
import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import type { BulkUpdatParams } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
import { DateOverrideInputDialog, DateOverrideList } from "@calcom/features/schedules";
import WebSchedule, {
@@ -106,7 +107,7 @@ type AvailabilitySettingsProps = {
bulkUpdateModalProps?: {
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
save: ({ eventTypeIds }: { eventTypeIds: number[] }) => void;
save: (params: BulkUpdatParams) => void;
isSaving: boolean;
};
};
@@ -0,0 +1,187 @@
"use client";
import { useReducer } from "react";
import { AppList } from "@calcom/features/apps/components/AppList";
import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { QueryCell } from "@calcom/trpc/components/QueryCell";
import { trpc } from "@calcom/trpc/react";
import { Button, EmptyScreen, showToast, SkeletonContainer, SkeletonText } from "@calcom/ui";
type ConferencingAppsViewWebWrapperProps = {
title: string;
description: string;
add: string;
};
type UpdateDefaultConferencingAppParams = { appSlug: string; callback: () => void };
type BulkUpdatParams = { eventTypeIds: number[]; callback: () => void };
type RemoveAppParams = { credentialId: number; teamId?: number; callback: () => void };
const SkeletonLoader = () => {
return (
<SkeletonContainer>
<div className="divide-subtle border-subtle space-y-6 rounded-b-lg border border-t-0 px-6 py-4">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
</div>
</SkeletonContainer>
);
};
type ModalState = {
isOpen: boolean;
credentialId: null | number;
};
export const ConferencingAppsViewWebWrapper = ({
title,
description,
add,
}: ConferencingAppsViewWebWrapperProps) => {
const { t } = useLocale();
const utils = trpc.useUtils();
const [modal, updateModal] = useReducer(
(data: ModalState, partialData: Partial<ModalState>) => ({ ...data, ...partialData }),
{
isOpen: false,
credentialId: null,
}
);
const handleModelClose = () => {
updateModal({ isOpen: false, credentialId: null });
};
const handleDisconnect = (credentialId: number) => {
updateModal({ isOpen: true, credentialId });
};
const installedIntegrationsQuery = trpc.viewer.integrations.useQuery({
variant: "conferencing",
onlyInstalled: true,
});
const { data: defaultConferencingApp } = trpc.viewer.getUsersDefaultConferencingApp.useQuery();
const deleteCredentialMutation = trpc.viewer.deleteCredential.useMutation();
const updateDefaultAppMutation = trpc.viewer.updateUserDefaultConferencingApp.useMutation();
const updateLocationsMutation = trpc.viewer.eventTypes.bulkUpdateToDefaultLocation.useMutation();
const handleRemoveApp = ({ credentialId, teamId, callback }: RemoveAppParams) => {
deleteCredentialMutation.mutate(
{ id: credentialId, teamId },
{
onSuccess: () => {
showToast(t("app_removed_successfully"), "success");
callback();
utils.viewer.integrations.invalidate();
utils.viewer.connectedCalendars.invalidate();
},
onError: () => {
showToast(t("error_removing_app"), "error");
callback();
},
}
);
};
const handleUpdateDefaultConferencingApp = ({ appSlug, callback }: UpdateDefaultConferencingAppParams) => {
updateDefaultAppMutation.mutate(
{ appSlug },
{
onSuccess: () => {
showToast("Default app updated successfully", "success");
utils.viewer.getUsersDefaultConferencingApp.invalidate();
callback();
},
onError: (error) => {
showToast(`Error: ${error.message}`, "error");
},
}
);
};
const handleBulkUpdateDefaultLocation = ({ eventTypeIds, callback }: BulkUpdatParams) => {
updateLocationsMutation.mutate(
{
eventTypeIds,
},
{
onSuccess: () => {
utils.viewer.getUsersDefaultConferencingApp.invalidate();
callback();
},
}
);
};
const AddConferencingButton = () => {
return (
<Button color="secondary" StartIcon="plus" href="/apps/categories/conferencing">
{add}
</Button>
);
};
return (
<SettingsHeader
title={title}
description={description}
CTA={<AddConferencingButton />}
borderInShellHeader={true}>
<>
<div className="bg-default w-full sm:mx-0 xl:mt-0">
<QueryCell
query={installedIntegrationsQuery}
customLoader={<SkeletonLoader />}
success={({ data }) => {
if (!data.items.length) {
return (
<EmptyScreen
Icon="calendar"
headline={t("no_category_apps", {
category: t("conferencing").toLowerCase(),
})}
description={t("no_category_apps_description_conferencing")}
buttonRaw={
<Button
color="secondary"
data-testid="connect-conferencing-apps"
href="/apps/categories/conferencing">
{t("connect_conference_apps")}
</Button>
}
/>
);
}
return (
<AppList
listClassName="rounded-lg rounded-t-none border-t-0 max-w-full"
handleDisconnect={handleDisconnect}
data={data}
variant="conferencing"
defaultConferencingApp={defaultConferencingApp}
handleUpdateDefaultConferencingApp={handleUpdateDefaultConferencingApp}
handleBulkUpdateDefaultLocation={handleBulkUpdateDefaultLocation}
isBulkUpdateDefaultLocationPending={updateDefaultAppMutation.isPending}
/>
);
}}
/>
</div>
<DisconnectIntegrationModal
handleModelClose={handleModelClose}
isOpen={modal.isOpen}
credentialId={modal.credentialId}
handleRemoveApp={handleRemoveApp}
/>
</>
</SettingsHeader>
);
};
+1
View File
@@ -10,6 +10,7 @@ export { SelectedCalendarsSettingsWebWrapper } from "./selected-calendars/wrappe
export { DestinationCalendarSettingsWebWrapper } from "./destination-calendar/wrappers/DestinationCalendarSettingsWebWrapper";
export * from "./availability";
export { EventTypeWebWrapper as EventType } from "./event-types/wrappers/EventTypeWebWrapper";
export { ConferencingAppsViewWebWrapper } from "./connect/conferencing-apps/ConferencingAppsViewWebWrapper";
export type { UpdateScheduleInput_2024_06_11 as UpdateScheduleBody } from "@calcom/platform-types";
export { Shell } from "./src/components/ui/shell";
export { AddMembersWithSwitchWebWrapper } from "./add-members-switch/AddMembersWithSwitchWebWrapper";
@@ -1,6 +1,7 @@
import Link from "next/link";
import React from "react";
import AppListCard from "@calcom/features/apps/components/AppListCard";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -8,7 +9,6 @@ import { QueryCell } from "@calcom/trpc/components/QueryCell";
import { trpc } from "@calcom/trpc/react";
import { Alert } from "@calcom/ui";
import { List } from "@calcom/ui";
import AppListCard from "@calcom/web/components/AppListCard";
import AdditionalCalendarSelector from "@calcom/web/components/apps/AdditionalCalendarSelector";
import { SelectedCalendarsSettings } from "../SelectedCalendarsSettings";
@@ -1,12 +1,11 @@
"use client";
import type { AppListCardProps } from "@calcom/features/apps/components/AppListCard";
import classNames from "@calcom/lib/classNames";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Avatar, Badge, Icon, ListItemText } from "@calcom/ui";
import type { AppListCardProps } from "../../../../apps/web/components/AppListCard";
export const AppListCard = (props: AppListCardProps & { highlight?: boolean }) => {
const { t } = useLocale();
const {