2022-12-07 20:53:44 +00:00
|
|
|
import { GetServerSidePropsContext } from "next";
|
2022-08-26 00:11:41 +00:00
|
|
|
import { Trans } from "next-i18next";
|
|
|
|
import Link from "next/link";
|
2022-09-09 12:57:31 +00:00
|
|
|
import { useRouter } from "next/router";
|
2022-08-26 00:11:41 +00:00
|
|
|
import { Fragment } from "react";
|
|
|
|
|
2022-12-01 00:59:27 +00:00
|
|
|
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
2022-10-18 18:06:26 +00:00
|
|
|
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
2023-01-05 17:00:16 +00:00
|
|
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
2022-08-26 00:11:41 +00:00
|
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
|
|
import { trpc } from "@calcom/trpc/react";
|
2022-11-23 02:55:25 +00:00
|
|
|
import {
|
|
|
|
Alert,
|
|
|
|
Badge,
|
|
|
|
Button,
|
|
|
|
EmptyScreen,
|
|
|
|
Icon,
|
|
|
|
List,
|
|
|
|
ListItem,
|
|
|
|
ListItemText,
|
|
|
|
ListItemTitle,
|
|
|
|
Meta,
|
|
|
|
SkeletonButton,
|
|
|
|
SkeletonContainer,
|
|
|
|
SkeletonText,
|
2022-12-07 14:37:20 +00:00
|
|
|
showToast,
|
2022-11-23 02:55:25 +00:00
|
|
|
} from "@calcom/ui";
|
2022-08-26 00:11:41 +00:00
|
|
|
|
|
|
|
import { QueryCell } from "@lib/QueryCell";
|
|
|
|
|
2022-11-01 13:29:01 +00:00
|
|
|
import { CalendarSwitch } from "@components/settings/CalendarSwitch";
|
2022-08-26 00:11:41 +00:00
|
|
|
|
2022-12-07 20:53:44 +00:00
|
|
|
import { ssrInit } from "@server/lib/ssr";
|
|
|
|
|
2022-09-15 09:05:26 +00:00
|
|
|
const SkeletonLoader = () => {
|
|
|
|
return (
|
|
|
|
<SkeletonContainer>
|
2022-10-04 14:56:38 +00:00
|
|
|
<div className="mt-6 mb-8 space-y-6">
|
2022-09-15 09:05:26 +00:00
|
|
|
<SkeletonText className="h-8 w-full" />
|
|
|
|
<SkeletonText className="h-8 w-full" />
|
|
|
|
<SkeletonText className="h-8 w-full" />
|
|
|
|
<SkeletonText className="h-8 w-full" />
|
|
|
|
|
|
|
|
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
|
|
|
</div>
|
|
|
|
</SkeletonContainer>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-10-20 10:03:39 +00:00
|
|
|
const AddCalendarButton = () => {
|
|
|
|
const { t } = useLocale();
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Button color="secondary" StartIcon={Icon.FiPlus} href="/apps/categories/calendar">
|
|
|
|
{t("add_calendar")}
|
|
|
|
</Button>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-08-26 00:11:41 +00:00
|
|
|
const CalendarsView = () => {
|
|
|
|
const { t } = useLocale();
|
2022-09-09 12:57:31 +00:00
|
|
|
const router = useRouter();
|
2022-08-26 00:11:41 +00:00
|
|
|
|
|
|
|
const utils = trpc.useContext();
|
|
|
|
|
2022-11-10 23:40:01 +00:00
|
|
|
const query = trpc.viewer.connectedCalendars.useQuery();
|
|
|
|
const mutation = trpc.viewer.setDestinationCalendar.useMutation({
|
2022-08-26 00:11:41 +00:00
|
|
|
async onSettled() {
|
2022-11-10 23:40:01 +00:00
|
|
|
await utils.viewer.connectedCalendars.invalidate();
|
2022-08-26 00:11:41 +00:00
|
|
|
},
|
2022-12-07 14:37:20 +00:00
|
|
|
onSuccess: async () => {
|
|
|
|
showToast(t("calendar_updated_successfully"), "success");
|
|
|
|
},
|
|
|
|
onError: () => {
|
|
|
|
showToast(t("unexpected_error_try_again"), "error");
|
|
|
|
},
|
2022-08-26 00:11:41 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
2022-08-30 19:46:52 +00:00
|
|
|
<>
|
2022-12-07 20:53:44 +00:00
|
|
|
<Meta title={t("calendars")} description={t("calendars_description")} CTA={<AddCalendarButton />} />
|
2022-08-30 19:46:52 +00:00
|
|
|
<QueryCell
|
|
|
|
query={query}
|
2022-09-15 09:05:26 +00:00
|
|
|
customLoader={<SkeletonLoader />}
|
2022-08-30 19:46:52 +00:00
|
|
|
success={({ data }) => {
|
|
|
|
return data.connectedCalendars.length ? (
|
|
|
|
<div>
|
2022-09-13 20:13:24 +00:00
|
|
|
<div className="mt-4 flex space-x-4 rounded-md border-gray-200 bg-gray-50 p-2 sm:mx-0 sm:p-10 md:border md:p-6 xl:mt-0">
|
|
|
|
<div className=" flex h-9 w-9 items-center justify-center rounded-md border-2 border-gray-200 bg-white p-[6px]">
|
|
|
|
<Icon.FiCalendar className="h-6 w-6" />
|
|
|
|
</div>
|
|
|
|
|
2022-10-18 18:06:26 +00:00
|
|
|
<div className="flex w-full flex-col space-y-3">
|
2022-09-13 20:13:24 +00:00
|
|
|
<div>
|
|
|
|
<h4 className=" pb-2 text-base font-semibold leading-5 text-black">
|
|
|
|
{t("add_to_calendar")}
|
|
|
|
</h4>
|
|
|
|
<p className=" text-sm leading-5 text-gray-600">
|
|
|
|
<Trans i18nKey="add_to_calendar_description">
|
|
|
|
Where to add events when you re booked. You can override this on a per-event basis in
|
|
|
|
advanced settings in the event type.
|
|
|
|
</Trans>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<DestinationCalendarSelector
|
|
|
|
hidePlaceholder
|
|
|
|
value={data.destinationCalendar?.externalId}
|
|
|
|
onChange={mutation.mutate}
|
|
|
|
isLoading={mutation.isLoading}
|
|
|
|
/>
|
2022-08-30 19:46:52 +00:00
|
|
|
</div>
|
2022-08-26 00:11:41 +00:00
|
|
|
</div>
|
2022-09-13 20:13:24 +00:00
|
|
|
<h4 className="mt-12 text-base font-semibold leading-5 text-black">
|
2022-08-30 19:46:52 +00:00
|
|
|
{t("check_for_conflicts")}
|
|
|
|
</h4>
|
2022-09-13 20:13:24 +00:00
|
|
|
<p className="pb-2 text-sm leading-5 text-gray-600">{t("select_calendars")}</p>
|
2022-12-05 20:39:52 +00:00
|
|
|
<List className="flex flex-col gap-6" noBorderTreatment>
|
2022-08-30 19:46:52 +00:00
|
|
|
{data.connectedCalendars.map((item) => (
|
|
|
|
<Fragment key={item.credentialId}>
|
2022-10-04 14:56:38 +00:00
|
|
|
{item.error && item.error.message && (
|
|
|
|
<Alert
|
|
|
|
severity="warning"
|
|
|
|
key={item.credentialId}
|
|
|
|
title={t("calendar_connection_fail")}
|
|
|
|
message={item.error.message}
|
|
|
|
className="mb-4 mt-4"
|
|
|
|
actions={
|
|
|
|
<>
|
|
|
|
{/* @TODO: add a reconnect button, that calls add api and delete old credential */}
|
|
|
|
<DisconnectIntegration
|
|
|
|
credentialId={item.credentialId}
|
|
|
|
trashIcon
|
|
|
|
onSuccess={() => query.refetch()}
|
|
|
|
buttonProps={{
|
|
|
|
className: "border border-gray-300 py-[2px]",
|
|
|
|
color: "secondary",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{item?.error === undefined && item.calendars && (
|
2022-12-05 20:39:52 +00:00
|
|
|
<ListItem className="flex-col rounded-md">
|
2022-11-30 21:41:29 +00:00
|
|
|
<div className="flex w-full flex-1 items-center space-x-3 p-4 rtl:space-x-reverse">
|
2022-08-30 19:46:52 +00:00
|
|
|
{
|
|
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
|
|
item.integration.logo && (
|
|
|
|
<img
|
|
|
|
className="h-10 w-10"
|
|
|
|
src={item.integration.logo}
|
|
|
|
alt={item.integration.title}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
<div className="flex-grow truncate pl-2">
|
2023-01-04 07:38:45 +00:00
|
|
|
<ListItemTitle component="h3" className="mb-1 space-x-2 rtl:space-x-reverse">
|
2022-08-30 19:46:52 +00:00
|
|
|
<Link href={"/apps/" + item.integration.slug}>
|
|
|
|
{item.integration.name || item.integration.title}
|
|
|
|
</Link>
|
|
|
|
{data?.destinationCalendar?.credentialId === item.credentialId && (
|
|
|
|
<Badge variant="green">Default</Badge>
|
|
|
|
)}
|
|
|
|
</ListItemTitle>
|
|
|
|
<ListItemText component="p">{item.integration.description}</ListItemText>
|
|
|
|
</div>
|
|
|
|
<div>
|
2022-09-13 20:13:24 +00:00
|
|
|
<DisconnectIntegration
|
|
|
|
trashIcon
|
|
|
|
credentialId={item.credentialId}
|
2022-12-05 20:39:52 +00:00
|
|
|
buttonProps={{ className: "border border-gray-300" }}
|
2022-09-13 20:13:24 +00:00
|
|
|
/>
|
2022-08-30 19:46:52 +00:00
|
|
|
</div>
|
2022-08-26 00:11:41 +00:00
|
|
|
</div>
|
2022-08-30 19:46:52 +00:00
|
|
|
<div className="w-full border-t border-gray-200">
|
|
|
|
<p className="px-2 pt-4 text-sm text-neutral-500">
|
|
|
|
{t("toggle_calendars_conflict")}
|
|
|
|
</p>
|
2022-12-05 20:39:52 +00:00
|
|
|
<ul className="space-y-2 p-4">
|
2022-08-30 19:46:52 +00:00
|
|
|
{item.calendars.map((cal) => (
|
|
|
|
<CalendarSwitch
|
|
|
|
key={cal.externalId}
|
|
|
|
externalId={cal.externalId}
|
|
|
|
title={cal.name || "Nameless calendar"}
|
|
|
|
type={item.integration.type}
|
2022-09-19 02:01:34 +00:00
|
|
|
isSelected={cal.isSelected}
|
2022-08-30 19:46:52 +00:00
|
|
|
defaultSelected={cal.externalId === data?.destinationCalendar?.externalId}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</ul>
|
2022-08-26 00:11:41 +00:00
|
|
|
</div>
|
2022-08-30 19:46:52 +00:00
|
|
|
</ListItem>
|
|
|
|
)}
|
|
|
|
</Fragment>
|
|
|
|
))}
|
|
|
|
</List>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<EmptyScreen
|
|
|
|
Icon={Icon.FiCalendar}
|
2022-09-15 09:05:26 +00:00
|
|
|
headline={t("no_calendar_installed")}
|
|
|
|
description={t("no_calendar_installed_description")}
|
|
|
|
buttonText={t("add_a_calendar")}
|
2022-10-20 10:03:39 +00:00
|
|
|
buttonOnClick={() => router.push("/apps/categories/calendar")}
|
2022-08-30 19:46:52 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}}
|
2022-10-04 14:56:38 +00:00
|
|
|
error={() => {
|
|
|
|
return (
|
|
|
|
<Alert
|
|
|
|
message={
|
|
|
|
<Trans i18nKey="fetching_calendars_error">
|
|
|
|
An error ocurred while fetching your Calendars.
|
|
|
|
<a className="cursor-pointer underline" onClick={() => query.refetch()}>
|
|
|
|
try again
|
|
|
|
</a>
|
|
|
|
.
|
|
|
|
</Trans>
|
|
|
|
}
|
|
|
|
severity="error"
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}}
|
2022-08-30 19:46:52 +00:00
|
|
|
/>
|
|
|
|
</>
|
2022-08-26 00:11:41 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
CalendarsView.getLayout = getLayout;
|
|
|
|
|
2022-12-07 20:53:44 +00:00
|
|
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
|
|
|
const ssr = await ssrInit(context);
|
|
|
|
|
|
|
|
return {
|
|
|
|
props: {
|
|
|
|
trpcState: ssr.dehydrate(),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-08-26 00:11:41 +00:00
|
|
|
export default CalendarsView;
|