import Image from "next/image"; import { Fragment, ReactNode, useState } from "react"; import { useMutation } from "react-query"; import { QueryCell } from "@lib/QueryCell"; import classNames from "@lib/classNames"; import { AddAppleIntegrationModal } from "@lib/integrations/Apple/components/AddAppleIntegration"; import { AddCalDavIntegrationModal } from "@lib/integrations/CalDav/components/AddCalDavIntegration"; import showToast from "@lib/notification"; import { trpc } from "@lib/trpc"; import { Dialog } from "@components/Dialog"; import { List, ListItem, ListItemText, ListItemTitle } from "@components/List"; import Shell, { ShellSubHeading } from "@components/Shell"; import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent"; import { Alert } from "@components/ui/Alert"; import Badge from "@components/ui/Badge"; import Button, { ButtonBaseProps } from "@components/ui/Button"; import Switch from "@components/ui/Switch"; function pluralize(opts: { num: number; plural: string; singular: string }) { if (opts.num === 0) { return opts.singular; } return opts.singular; } function SubHeadingTitleWithConnections(props: { title: ReactNode; numConnections?: number }) { const num = props.numConnections; return ( <> {props.title} {num ? ( {num}{" "} {pluralize({ num, singular: "connection", plural: "connections", })} ) : null} ); } function ConnectIntegration(props: { type: string; render: (renderProps: ButtonBaseProps) => JSX.Element }) { const { type } = props; const [isLoading, setIsLoading] = useState(false); const mutation = useMutation(async () => { const res = await fetch("/api/integrations/" + type.replace("_", "") + "/add"); if (!res.ok) { throw new Error("Something went wrong"); } const json = await res.json(); window.location.href = json.url; setIsLoading(true); }); const [isModalOpen, _setIsModalOpen] = useState(false); const utils = trpc.useContext(); const setIsModalOpen: typeof _setIsModalOpen = (v) => { _setIsModalOpen(v); // refetch intergrations on modal toggles utils.invalidateQueries(["viewer.integrations"]); }; return ( <> {props.render({ onClick() { if (["caldav_calendar", "apple_calendar"].includes(type)) { // special handlers setIsModalOpen(true); return; } mutation.mutate(); }, loading: mutation.isLoading || isLoading, disabled: isModalOpen, })} {type === "caldav_calendar" && ( )} {type === "apple_calendar" && ( )} ); } function DisconnectIntegration(props: { /** * Integration credential id */ id: number; render: (renderProps: ButtonBaseProps) => JSX.Element; }) { const utils = trpc.useContext(); const [modalOpen, setModalOpen] = useState(false); const mutation = useMutation( async () => { const res = await fetch("/api/integrations", { method: "DELETE", body: JSON.stringify({ id: props.id }), headers: { "Content-Type": "application/json", }, }); if (!res.ok) { throw new Error("Something went wrong"); } }, { async onSettled() { await utils.invalidateQueries(["viewer.integrations"]); }, onSuccess() { setModalOpen(false); }, } ); return ( <> { mutation.mutate(); }}> Are you sure you want to disconnect this integration? {props.render({ onClick() { setModalOpen(true); }, disabled: modalOpen, loading: mutation.isLoading, })} ); } function ConnectOrDisconnectIntegrationButton(props: { // credentialIds: number[]; type: string; installed: boolean; }) { const [credentialId] = props.credentialIds; if (credentialId) { return ( ( )} /> ); } if (!props.installed) { return (
); } /** We don't need to "Connect", just show that it's installed */ if (props.type === "daily_video") { return (

Installed

); } return ( ( )} /> ); } function IntegrationListItem(props: { imageSrc: string; title: string; description: string; actions?: ReactNode; children?: ReactNode; }) { return (
{props.title}
{props.title} {props.description}
{props.actions}
{props.children &&
{props.children}
}
); } export function CalendarSwitch(props: { type: string; externalId: string; title: string; defaultSelected: boolean; }) { const utils = trpc.useContext(); const mutation = useMutation< unknown, unknown, { isOn: boolean; } >( async ({ isOn }) => { const body = { integration: props.type, externalId: props.externalId, }; if (isOn) { const res = await fetch("/api/availability/calendar", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), }); if (!res.ok) { throw new Error("Something went wrong"); } } else { const res = await fetch("/api/availability/calendar", { method: "DELETE", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), }); if (!res.ok) { throw new Error("Something went wrong"); } } }, { async onSettled() { await utils.invalidateQueries(["viewer.integrations"]); }, onError() { showToast(`Something went wrong when toggling "${props.title}""`, "error"); }, } ); return (
{ mutation.mutate({ isOn }); }} />
); } export default function IntegrationsPage() { const query = trpc.useQuery(["viewer.integrations"]); return ( { return ( <> } /> {data.conferencing.items.map((item) => ( } /> ))} } /> {data.payment.items.map((item) => ( } /> ))} } subtitle={ <> Configure how your links integrate with your calendars.
You can override these settings on a per event basis. } /> {data.connectedCalendars.length > 0 && ( <> {data.connectedCalendars.map((item) => ( {item.calendars ? ( ( )} /> }>
    {item.calendars.map((cal) => ( ))}
) : ( ( )} /> } /> )}
))}
} /> )} {data.calendar.items.map((item) => ( ( )} /> } /> ))} ); }} />
); }