cal.pub0.org/apps/web/pages/settings/my-account/conferencing.tsx

211 lines
7.5 KiB
TypeScript

import { useCallback, useState } from "react";
import type { EventLocationType } from "@calcom/app-store/locations";
import { getEventLocationTypeFromApp } from "@calcom/app-store/locations";
import { AppSetDefaultLinkDialog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
import { BulkEditDefaultConferencingModal } from "@calcom/features/eventtypes/components/BulkEditDefaultConferencingModal";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
Dropdown,
DropdownMenuContent,
DropdownMenuItem,
DropdownItem,
DropdownMenuTrigger,
List,
Meta,
showToast,
SkeletonContainer,
SkeletonText,
} from "@calcom/ui";
import { AlertCircle, MoreHorizontal, Trash, Video, Plus } from "@calcom/ui/components/icon";
import AppListCard from "@components/AppListCard";
import PageWrapper from "@components/PageWrapper";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="divide-subtle mb-8 mt-6 space-y-6">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
</div>
</SkeletonContainer>
);
};
const AddConferencingButton = () => {
const { t } = useLocale();
return (
<>
<Button color="secondary" StartIcon={Plus} href="/apps/categories/video">
{t("add_conferencing_app")}
</Button>
</>
);
};
const ConferencingLayout = () => {
const { t } = useLocale();
const utils = trpc.useContext();
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");
utils.viewer.integrations.invalidate({ variant: "conferencing", onlyInstalled: true });
setDeleteAppModal(false);
},
onError: () => {
showToast("Error deleting app", "error");
setDeleteAppModal(false);
},
});
const onSuccessCallback = useCallback(() => {
setBulkUpdateModal(true);
showToast("Default app updated successfully", "success");
}, []);
const updateDefaultAppMutation = trpc.viewer.updateUserDefaultConferencingApp.useMutation({
onSuccess: async () => {
await utils.viewer.getUsersDefaultConferencingApp.invalidate();
onSuccessCallback();
},
onError: (error) => {
showToast(`Error: ${error.message}`, "error");
},
});
const [deleteAppModal, setDeleteAppModal] = useState(false);
const [bulkUpdateModal, setBulkUpdateModal] = useState(false);
const [locationType, setLocationType] = useState<(EventLocationType & { slug: string }) | undefined>(
undefined
);
const [deleteCredentialId, setDeleteCredentialId] = useState<number>(0);
if (isLoading || defaultConferencingAppLoading)
return <SkeletonLoader title={t("conferencing")} description={t("conferencing_description")} />;
return (
<div className="bg-default w-full sm:mx-0 xl:mt-0">
<Meta
title={t("conferencing")}
description={t("conferencing_description")}
CTA={<AddConferencingButton />}
/>
<List>
{apps?.items &&
apps.items
.map((app) => ({ ...app, title: app.title || app.name }))
.map((app) => {
const appSlug = app?.slug;
const appIsDefault =
appSlug === defaultConferencingApp?.appSlug ||
(appSlug === "daily-video" && !defaultConferencingApp?.appSlug); // Default to cal video if the user doesnt have it set (we do this on new account creation but not old)
return (
<AppListCard
description={app.description}
title={app.title}
logo={app.logo}
key={app.title}
isDefault={appIsDefault} // @TODO: Handle when a user doesnt have this value set
actions={
<div>
<Dropdown>
<DropdownMenuTrigger asChild>
<Button StartIcon={MoreHorizontal} variant="icon" color="secondary" />
</DropdownMenuTrigger>
<DropdownMenuContent>
{!appIsDefault && (
<DropdownMenuItem>
<DropdownItem
type="button"
color="secondary"
StartIcon={Video}
onClick={() => {
const locationType = getEventLocationTypeFromApp(
app?.locationOption?.value ?? ""
);
if (locationType?.linkType === "static") {
setLocationType({ ...locationType, slug: appSlug });
} else {
updateDefaultAppMutation.mutate({
appSlug,
});
}
}}>
{t("set_as_default")}
</DropdownItem>
</DropdownMenuItem>
)}
<DropdownMenuItem>
<DropdownItem
type="button"
color="destructive"
disabled={app.isGlobal}
StartIcon={Trash}
onClick={() => {
setDeleteCredentialId(app.credentialIds[0]);
setDeleteAppModal(true);
}}>
{t("remove_app")}
</DropdownItem>
</DropdownMenuItem>
</DropdownMenuContent>
</Dropdown>
</div>
}
/>
);
})}
</List>
<Dialog open={deleteAppModal} onOpenChange={setDeleteAppModal}>
<DialogContent
title={t("Remove app")}
description={t("are_you_sure_you_want_to_remove_this_app")}
type="confirmation"
Icon={AlertCircle}>
<DialogFooter>
<Button color="primary" onClick={() => deleteAppMutation.mutate({ id: deleteCredentialId })}>
{t("yes_remove_app")}
</Button>
<DialogClose />
</DialogFooter>
</DialogContent>
</Dialog>
{locationType && (
<AppSetDefaultLinkDialog
locationType={locationType}
setLocationType={setLocationType}
onSuccess={onSuccessCallback}
/>
)}
{bulkUpdateModal && (
<BulkEditDefaultConferencingModal open={bulkUpdateModal} setOpen={setBulkUpdateModal} />
)}
</div>
);
};
ConferencingLayout.getLayout = getLayout;
ConferencingLayout.PageWrapper = PageWrapper;
export default ConferencingLayout;