Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 73 additions & 67 deletions apps/web/components/apps/CalendarListContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
"use client";

import { useEffect, Suspense } from "react";

import { InstallAppButton } from "@calcom/app-store/InstallAppButton";
import { DestinationCalendarSettingsWebWrapper } from "./DestinationCalendarSettingsWebWrapper";
import { SelectedCalendarsSettingsWebWrapper } from "@calcom/web/modules/calendars/components/SelectedCalendarsSettingsWebWrapper";
import AppListCard from "@calcom/web/modules/apps/components/AppListCard";
import { SkeletonLoader } from "@calcom/web/modules/apps/components/SkeletonLoader";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/components/button";
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
import { ShellSubHeading } from "@calcom/ui/components/layout";
import { List } from "@calcom/ui/components/list";
import { showToast } from "@calcom/ui/components/toast";
import { revalidateSettingsCalendars } from "@calcom/web/app/cache/path/settings/my-account";
import AppListCard from "@calcom/web/modules/apps/components/AppListCard";
import { SkeletonLoader } from "@calcom/web/modules/apps/components/SkeletonLoader";
import { SelectedCalendarsSettingsWebWrapper } from "@calcom/web/modules/calendars/components/SelectedCalendarsSettingsWebWrapper";
import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingTitleWithConnections";
import useRouterQuery from "@lib/hooks/useRouterQuery";

import { QueryCell } from "@lib/QueryCell";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import { Suspense, useEffect } from "react";
import { DestinationCalendarSettingsWebWrapper } from "./DestinationCalendarSettingsWebWrapper";

import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingTitleWithConnections";
type CalendarListContainerProps = {
connectedCalendars: RouterOutputs["viewer"]["calendars"]["connectedCalendars"];
installedCalendars: RouterOutputs["viewer"]["apps"]["integrations"];
heading?: boolean;
fromOnboarding?: boolean;
};

type Props = {
onChanged: () => unknown | Promise<unknown>;
Expand All @@ -30,7 +35,7 @@ type Props = {
isPending?: boolean;
};

function CalendarList(props: Props) {
function CalendarList(props: Props): JSX.Element {
const { t } = useLocale();
const query = trpc.viewer.apps.integrations.useQuery({ variant: "calendar", onlyInstalled: false });

Expand Down Expand Up @@ -66,18 +71,16 @@ function CalendarList(props: Props) {
);
}

const AddCalendarButton = () => {
const AddCalendarButton = (): JSX.Element => {
const { t } = useLocale();
return (
<>
<Button color="secondary" StartIcon="plus" href="/apps/categories/calendar">
{t("add_calendar")}
</Button>
</>
<Button color="secondary" StartIcon="plus" href="/apps/categories/calendar">
{t("add_calendar")}
</Button>
);
};

export const CalendarListContainerSkeletonLoader = () => {
export const CalendarListContainerSkeletonLoader = (): JSX.Element => {
const { t } = useLocale();
return (
<SettingsHeader
Expand All @@ -89,19 +92,12 @@ export const CalendarListContainerSkeletonLoader = () => {
);
};

type CalendarListContainerProps = {
connectedCalendars: RouterOutputs["viewer"]["calendars"]["connectedCalendars"];
installedCalendars: RouterOutputs["viewer"]["apps"]["integrations"];
heading?: boolean;
fromOnboarding?: boolean;
};

export function CalendarListContainer({
connectedCalendars: data,
installedCalendars,
heading = true,
fromOnboarding,
}: CalendarListContainerProps) {
}: CalendarListContainerProps): JSX.Element {
const { t } = useLocale();
const { error, setQuery: setError } = useRouterQuery("error");

Expand All @@ -113,7 +109,7 @@ export function CalendarListContainer({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const utils = trpc.useUtils();
const onChanged = () =>
const onChanged = (): void => {
Promise.allSettled([
utils.viewer.apps.integrations.invalidate(
{ variant: "calendar", onlyInstalled: true },
Expand All @@ -124,59 +120,69 @@ export function CalendarListContainer({
utils.viewer.calendars.connectedCalendars.invalidate(),
revalidateSettingsCalendars(),
]);

};
const mutation = trpc.viewer.calendars.setDestinationCalendar.useMutation({
onSuccess: () => {
utils.viewer.calendars.connectedCalendars.invalidate();
revalidateSettingsCalendars();
},
});

let content = null;
if (!!data.connectedCalendars.length || !!installedCalendars?.items.length) {
let headingContent = null;
if (heading) {
headingContent = (
<>
<DestinationCalendarSettingsWebWrapper connectedCalendars={data} />
<Suspense fallback={<SkeletonLoader />}>
<SelectedCalendarsSettingsWebWrapper
onChanged={onChanged}
fromOnboarding={fromOnboarding}
destinationCalendarId={data.destinationCalendar?.externalId}
isPending={mutation.isPending}
connectedCalendars={data}
/>
</Suspense>
</>
);
}
content = headingContent;
} else if (fromOnboarding) {
content = (
<>
{!!data?.connectedCalendars.length && (
<ShellSubHeading
className="mt-4"
title={<SubHeadingTitleWithConnections title={t("connect_additional_calendar")} />}
/>
)}
<CalendarList onChanged={onChanged} />
</>
);
} else {
content = (
<EmptyScreen
Icon="calendar"
headline={t("no_category_apps", {
category: t("calendar").toLowerCase(),
})}
description={t(`no_category_apps_description_calendar`)}
buttonRaw={
<Button color="secondary" data-testid="connect-calendar-apps" href="/apps/categories/calendar">
{t(`connect_calendar_apps`)}
</Button>
}
/>
);
}

return (
<SettingsHeader
title={t("calendars")}
description={t("calendars_description")}
CTA={<AddCalendarButton />}>
{!!data.connectedCalendars.length || !!installedCalendars?.items.length ? (
<>
{heading && (
<>
<DestinationCalendarSettingsWebWrapper />
<Suspense fallback={<SkeletonLoader />}>
<SelectedCalendarsSettingsWebWrapper
onChanged={onChanged}
fromOnboarding={fromOnboarding}
destinationCalendarId={data.destinationCalendar?.externalId}
isPending={mutation.isPending}
/>
</Suspense>
</>
)}
</>
) : fromOnboarding ? (
<>
{!!data?.connectedCalendars.length && (
<ShellSubHeading
className="mt-4"
title={<SubHeadingTitleWithConnections title={t("connect_additional_calendar")} />}
/>
)}
<CalendarList onChanged={onChanged} />
</>
) : (
<EmptyScreen
Icon="calendar"
headline={t("no_category_apps", {
category: t("calendar").toLowerCase(),
})}
description={t(`no_category_apps_description_calendar`)}
buttonRaw={
<Button color="secondary" data-testid="connect-calendar-apps" href="/apps/categories/calendar">
{t(`connect_calendar_apps`)}
</Button>
}
/>
)}
{content}
</SettingsHeader>
);
}
26 changes: 16 additions & 10 deletions apps/web/components/apps/DestinationCalendarSettingsWebWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import {
reminderSchema,
type ReminderMinutes,
reminderSchema,
} from "@calcom/trpc/server/routers/viewer/calendars/setDestinationReminder.schema";
import { showToast } from "@calcom/ui/components/toast";

import { AtomsWrapper } from "../../../../packages/platform/atoms/src/components/atoms-wrapper";
import { DestinationCalendarSettings } from "../../../../packages/platform/atoms/destination-calendar/DestinationCalendar";

export const DestinationCalendarSettingsWebWrapper = () => {
import { AtomsWrapper } from "../../../../packages/platform/atoms/src/components/atoms-wrapper";
export const DestinationCalendarSettingsWebWrapper = ({
connectedCalendars,
}: {
connectedCalendars?: RouterOutputs["viewer"]["calendars"]["connectedCalendars"];
}): JSX.Element | null => {
const { t } = useLocale();
const calendars = trpc.viewer.calendars.connectedCalendars.useQuery();
const calendars = trpc.viewer.calendars.connectedCalendars.useQuery(undefined, {
initialData: connectedCalendars,
});
const utils = trpc.useUtils();
const mutation = trpc.viewer.calendars.setDestinationCalendar.useMutation({
onSuccess: () => {
Expand All @@ -33,7 +38,7 @@ export const DestinationCalendarSettingsWebWrapper = () => {
return null;
}

const handleReminderChange = (value: ReminderMinutes) => {
const handleReminderChange = (value: ReminderMinutes): void => {
const destCal = calendars.data.destinationCalendar;
if (destCal?.credentialId) {
reminderMutation.mutate({
Expand All @@ -47,9 +52,10 @@ export const DestinationCalendarSettingsWebWrapper = () => {
const validatedReminderValue = reminderSchema.safeParse(
calendars.data.destinationCalendar.customCalendarReminder
);
const reminderValue: ReminderMinutes = validatedReminderValue.success
? validatedReminderValue.data
: null;
let reminderValue: ReminderMinutes = null;
if (validatedReminderValue.success) {
reminderValue = validatedReminderValue.data;
}

return (
<AtomsWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import Link from "next/link";
import React from "react";

import AppListCard from "@calcom/web/modules/apps/components/AppListCard";
import CredentialActionsDropdown from "@calcom/web/modules/apps/components/CredentialActionsDropdown";
import { SelectedCalendarsSettings } from "@calcom/atoms/selected-calendars/SelectedCalendarsSettings";
import AdditionalCalendarSelector from "@calcom/features/calendars/AdditionalCalendarSelector";
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
import { useLocale } from "@calcom/lib/hooks/useLocale";
Expand All @@ -11,8 +7,10 @@ import { trpc } from "@calcom/trpc/react";
import { Alert } from "@calcom/ui/components/alert";
import { Select } from "@calcom/ui/components/form";
import { List } from "@calcom/ui/components/list";

import { SelectedCalendarsSettings } from "@calcom/atoms/selected-calendars/SelectedCalendarsSettings";
import AppListCard from "@calcom/web/modules/apps/components/AppListCard";
import CredentialActionsDropdown from "@calcom/web/modules/apps/components/CredentialActionsDropdown";
import Link from "next/link";
import React from "react";

export enum SelectedCalendarSettingsScope {
User = "user",
Expand All @@ -30,6 +28,7 @@ type SelectedCalendarsSettingsWebWrapperProps = {
scope?: SelectedCalendarSettingsScope;
setScope?: (scope: SelectedCalendarSettingsScope) => void;
disableConnectionModification?: boolean;
connectedCalendars?: RouterOutputs["viewer"]["calendars"]["connectedCalendars"];
};

const ConnectedCalendarList = ({
Expand Down Expand Up @@ -92,7 +91,12 @@ const ConnectedCalendarList = ({
isChecked={cal.isSelected}
destination={cal.externalId === destinationCalendarId}
credentialId={cal.credentialId}
eventTypeId={shouldUseEventTypeScope ? eventTypeId : null}
eventTypeId={(() => {
if (shouldUseEventTypeScope) {
return eventTypeId;
}
return null;
})()}
delegationCredentialId={connectedCalendar.delegationCredentialId || null}
/>
))}
Expand Down Expand Up @@ -145,19 +149,30 @@ export const SelectedCalendarsSettingsWebWrapper = (props: SelectedCalendarsSett
eventTypeId = null,
} = props;

const query = trpc.viewer.calendars.connectedCalendars.useQuery(
{
eventTypeId: scope === SelectedCalendarSettingsScope.EventType ? eventTypeId! : null,
},
{
suspense: true,
refetchOnWindowFocus: false,
}
);
let queryInput: { eventTypeId: number } | undefined;
if (scope === SelectedCalendarSettingsScope.EventType) {
queryInput = { eventTypeId: eventTypeId! };
}

let initialData: RouterOutputs["viewer"]["calendars"]["connectedCalendars"] | undefined;
if (scope === SelectedCalendarSettingsScope.User && props.connectedCalendars) {
initialData = props.connectedCalendars;
}

const { isPending } = props;
const query = trpc.viewer.calendars.connectedCalendars.useQuery(queryInput, {
initialData,
suspense: true,
refetchOnWindowFocus: false,
});

const isPending = props.isPending;
const showScopeSelector = !!props.eventTypeId;
const isDisabled = disabledScope ? disabledScope === scope : false;

let isDisabled = false;
if (disabledScope) {
isDisabled = disabledScope === scope;
}

const shouldDisableConnectionModification = isDisabled || disableConnectionModification;
return (
<div>
Expand All @@ -170,7 +185,7 @@ export const SelectedCalendarsSettingsWebWrapper = (props: SelectedCalendarsSett
scope={scope}
shouldDisableConnectionModification={shouldDisableConnectionModification}
/>
{query.data?.connectedCalendars && query.data?.connectedCalendars.length > 0 ? (
{!!(query.data?.connectedCalendars && query.data?.connectedCalendars.length > 0) && (
<ConnectedCalendarList
fromOnboarding={props.fromOnboarding}
scope={scope}
Expand All @@ -180,7 +195,7 @@ export const SelectedCalendarsSettingsWebWrapper = (props: SelectedCalendarsSett
items={query.data.connectedCalendars}
isDisabled={isDisabled}
/>
) : null}
)}
</SelectedCalendarsSettings>
</div>
);
Expand Down
Loading
Loading