* Add endpoints for testing the flow * Add MVP * new route * Fixes * Fixes * Remove enable toggle support from domainWideDelegation * Fixes * Revert "Remove enable toggle support from domainWideDelegation" This reverts commit c29e729206cd1fa063f9c9ce0cf148ef1577d60b. * Revert yarn.lock * More fixes * Fix new workspace platform add * refactor: improvements * refactor: bug fixes and improvements * fix: type errors * fix: conflicts * chore: update test * fix: logic * chore: improvements * fix: toglle * fix: bugs * fix: type err * chore: check number type * fix: after conflicts * chore: fix type err * fix: type errors and tests * fix: tets * test * chore: remove unused * fix: google meet url on booking page and secondary calendar * fix: add property * fix: type err * fix: re assingment bug * fix: use getAllCredentials * chore: fix import * fix: installed count * fix: pass event type * fix: import * fix: [Stacked PR] Review fixes (#17958) * Review fixes * fix: destination calendar bug --------- Co-authored-by: Udit Takkar <udit222001@gmail.com> * refactor: use repository * chore: remove duplicate * fix: More review fixes for domain wide delegation (#17969) * Reuse buildCredentialPayloadForCalendar * fixes --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> * fix ts error * Fix getSchedule not using dwd credentials (#17995) * fix: Remove direct DWD table access from google-calendar and remove delegatedToId column from Credential (#18015) * Remove fn rename to reduce number of files changed * chore: check feature globally * test: add unit tests * chore: move function * test: add booking test * wip * Remove domain-wide-delegation team feature flag * Make sure duplicate calendars are not shown due to DWD. Show DWD when there is conflict * Fix tests and their ts errors * Fix more tests * Move findUsersForAvailabilityCheck to separate file as it has AppStore dependency causing problem with Routing Forms. Build crashes * fix: Multiple calendar connections from Google not showing up in apps/installed * DestinationCalendar must have either credentialId or domainWideDeelgationId. Also handle the case when DWD is disabled and there was a non-dwd credential that could be used * Disable deletetion of DWD as it is destructive and prefer disabling instead * Show DWD credential calendars at the top * Fixed tests * Calendar Cache DWD support * Self-Review: Verify email required and add more selectedCalendar tests * Self Review: shorten names * Self ReviewL Dead code removal * Revert "Calendar Cache DWD support" This reverts commit 009f236470fa21eba5986117d4f6e4d4c5e38c34. * Some misc fixes * fixes * Performance improvememt in slots loading and booking * More cases for handling dwd credentials * simplify the logic. Ensure that EventManager and the modules below it in the flow have CredentialForCalendarService available * Fix tests * Fix installed/conferencing not showing gogole meet * Shorten name * Fix ApI v2 tests * Add some more tests * add getSchedule tests * Improve tests * Make Google Meet default when DWD for google is enabled * Enable feature flagging for DWD * chore: bump libraries * Encrypt serviceaccount private key * Fix ts errors * bump platform libraries * org add dwd * org add dwd * bump platform libraries * fix selected calendars * fix remove selected dwd calendar * remove oauthclient id aliad in authedCalendar * remove oauthclient id aliad in authedCalendar * refactor: OrganizationsDwdController * chore: export toggleDwdEnabled from platform-libraries * feat: v2 update (enable / disable) dwd * refactor: SelectedCalendarsController check if user belongs to dwd org & for not dwd use previous logic * wip: DestinationCalendar send domainWideDelegationCredentialId from frontend to api * try fix set destination calendar api v2 * fixup! try fix set destination calendar api v2 * setting google meet as default location working for dwd * bump platform libraries version * allow office 365 workspace slug * allow office 365 workspace slug * chore: v2 create dwd MS and Google service keys input * fix: CreateDwdInput serviceAccountKey * fix: CredentialForCalendarService type * fix: check workspace slug * feat: update serviceAccessKey of DWD * testing * fixup! testing * fix: getAuthUtl bug * fix: unit tests * fix: type err and unit test * fix: e2e test * fix: credentials bug and failing unit tests * Update CalendarService.ts * chore: refactor office365 calendar service and add testDomainWideDelegationSetup * fix: office365Calendar use correct clientId/Secret for DWD * fix: office365Calendar dwd no need refresh token * test: add unit test for outlook dwd and setup * feat: added dwd support for office365 calendar * feat: added dwd support for office365 video * test: finish test for outlook dwd * fix: create dialog bug * chore: remove console logs * fix: refreshToken bug * bump version libraries * refactor: fetch dwd credentials only in findQualifiedHosts * fixup! refactor: fetch dwd credentials only in findQualifiedHosts * fix: type err * fix: type err * fix: getUserDisplayName * fix: unit test * chore: bump platform lib --------- Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Udit Takkar <udit222001@gmail.com> Co-authored-by: Morgan Vernay <morgan@cal.com> Co-authored-by: supalarry <laurisskraucis@gmail.com> Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
261 lines
9.0 KiB
TypeScript
261 lines
9.0 KiB
TypeScript
import { useCallback, useState } from "react";
|
|
|
|
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 type { UpdateUsersDefaultConferencingAppParams } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
|
|
import { AppSetDefaultLinkDialog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
|
|
import type {
|
|
BulkUpdatParams,
|
|
EventTypes,
|
|
} from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
|
|
import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
|
|
import { isDwdCredential } from "@calcom/lib/domainWideDelegation/clientAndServer";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
import type { AppCategories } from "@calcom/prisma/enums";
|
|
import { type RouterOutputs } from "@calcom/trpc";
|
|
import type { App } from "@calcom/types/App";
|
|
import {
|
|
Alert,
|
|
Button,
|
|
Dropdown,
|
|
DropdownItem,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
List,
|
|
showToast,
|
|
} from "@calcom/ui";
|
|
|
|
export type HandleDisconnect = (credentialId: number, app: App["slug"], teamId?: number) => void;
|
|
|
|
interface AppListProps {
|
|
variant?: AppCategories;
|
|
data: RouterOutputs["viewer"]["integrations"];
|
|
handleDisconnect: HandleDisconnect;
|
|
listClassName?: string;
|
|
defaultConferencingApp: RouterOutputs["viewer"]["getUsersDefaultConferencingApp"];
|
|
handleUpdateUserDefaultConferencingApp: (params: UpdateUsersDefaultConferencingAppParams) => void;
|
|
handleBulkUpdateDefaultLocation: (params: BulkUpdatParams) => void;
|
|
isBulkUpdateDefaultLocationPending: boolean;
|
|
eventTypes?: EventTypes;
|
|
isEventTypesFetching?: boolean;
|
|
handleConnectDisconnectIntegrationMenuToggle: () => void;
|
|
handleBulkEditDialogToggle: () => void;
|
|
}
|
|
|
|
export const AppList = ({
|
|
data,
|
|
handleDisconnect,
|
|
variant,
|
|
listClassName,
|
|
defaultConferencingApp,
|
|
handleUpdateUserDefaultConferencingApp,
|
|
handleBulkUpdateDefaultLocation,
|
|
isBulkUpdateDefaultLocationPending,
|
|
eventTypes,
|
|
isEventTypesFetching,
|
|
handleConnectDisconnectIntegrationMenuToggle,
|
|
handleBulkEditDialogToggle,
|
|
}: AppListProps) => {
|
|
const [bulkUpdateModal, setBulkUpdateModal] = useState(false);
|
|
const [locationType, setLocationType] = useState<(EventLocationType & { slug: string }) | undefined>(
|
|
undefined
|
|
);
|
|
const onSuccessCallback = useCallback(() => {
|
|
setBulkUpdateModal(true);
|
|
showToast("Default app updated successfully", "success");
|
|
}, []);
|
|
|
|
const ChildAppCard = ({
|
|
item,
|
|
}: {
|
|
item: RouterOutputs["viewer"]["integrations"]["items"][number] & {
|
|
credentialOwner?: CredentialOwner;
|
|
};
|
|
}) => {
|
|
const appSlug = item?.slug;
|
|
const appIsDefault =
|
|
appSlug === defaultConferencingApp?.appSlug ||
|
|
(appSlug === "daily-video" && !defaultConferencingApp?.appSlug);
|
|
return (
|
|
<AppListCard
|
|
key={item.name}
|
|
description={item.description}
|
|
title={item.name}
|
|
logo={item.logo}
|
|
isDefault={appIsDefault}
|
|
shouldHighlight
|
|
slug={item.slug}
|
|
invalidCredential={item?.invalidCredentialIds ? item.invalidCredentialIds.length > 0 : false}
|
|
credentialOwner={item?.credentialOwner}
|
|
actions={
|
|
!item.credentialOwner?.readOnly ? (
|
|
<div className="flex justify-end">
|
|
<Dropdown modal={false}>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button StartIcon="ellipsis" variant="icon" color="secondary" />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent>
|
|
{!appIsDefault && variant === "conferencing" && !item.credentialOwner?.teamId && (
|
|
<DropdownMenuItem>
|
|
<DropdownItem
|
|
type="button"
|
|
color="secondary"
|
|
StartIcon="video"
|
|
onClick={() => {
|
|
const locationType = getLocationFromApp(item?.locationOption?.value ?? "");
|
|
if (locationType?.linkType === "static") {
|
|
setLocationType({ ...locationType, slug: appSlug });
|
|
} else {
|
|
handleUpdateUserDefaultConferencingApp({
|
|
appSlug,
|
|
onSuccessCallback: () => setBulkUpdateModal(true),
|
|
onErrorCallback: () => {
|
|
return;
|
|
},
|
|
});
|
|
}
|
|
}}>
|
|
{t("set_as_default")}
|
|
</DropdownItem>
|
|
</DropdownMenuItem>
|
|
)}
|
|
<ConnectOrDisconnectIntegrationMenuItem
|
|
credentialId={item.credentialOwner?.credentialId || item.userCredentialIds[0]}
|
|
type={item.type}
|
|
app={item.slug}
|
|
isGlobal={item.isGlobal}
|
|
installed
|
|
invalidCredentialIds={item.invalidCredentialIds}
|
|
handleDisconnect={handleDisconnect}
|
|
teamId={item.credentialOwner ? item.credentialOwner?.teamId : undefined}
|
|
handleConnectDisconnectIntegrationMenuToggle={
|
|
handleConnectDisconnectIntegrationMenuToggle
|
|
}
|
|
/>
|
|
</DropdownMenuContent>
|
|
</Dropdown>
|
|
</div>
|
|
) : null
|
|
}>
|
|
<AppSettings slug={item.slug} />
|
|
</AppListCard>
|
|
);
|
|
};
|
|
|
|
const appsWithTeamCredentials = data.items.filter((app) => app.teams.length);
|
|
const cardsForAppsWithTeams = appsWithTeamCredentials.map((app) => {
|
|
const appCards = [];
|
|
|
|
if (app.userCredentialIds.length) {
|
|
appCards.push(<ChildAppCard item={app} />);
|
|
}
|
|
for (const team of app.teams) {
|
|
if (team) {
|
|
appCards.push(
|
|
<ChildAppCard
|
|
item={{
|
|
...app,
|
|
credentialOwner: {
|
|
name: team.name,
|
|
avatar: team.logoUrl,
|
|
teamId: team.teamId,
|
|
credentialId: team.credentialId,
|
|
readOnly: !team.isAdmin,
|
|
},
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
return appCards;
|
|
});
|
|
|
|
const { t } = useLocale();
|
|
return (
|
|
<>
|
|
<List className={listClassName}>
|
|
{cardsForAppsWithTeams.map((apps) => apps.map((cards) => cards))}
|
|
{data.items
|
|
.filter((item) => item.invalidCredentialIds)
|
|
.map((item, i) => {
|
|
if (!item.teams.length) return <ChildAppCard key={i} item={item} />;
|
|
})}
|
|
</List>
|
|
{locationType && (
|
|
<AppSetDefaultLinkDialog
|
|
locationType={locationType}
|
|
setLocationType={() => setLocationType(undefined)}
|
|
onSuccess={onSuccessCallback}
|
|
handleUpdateUserDefaultConferencingApp={handleUpdateUserDefaultConferencingApp}
|
|
/>
|
|
)}
|
|
|
|
{bulkUpdateModal && (
|
|
<BulkEditDefaultForEventsModal
|
|
bulkUpdateFunction={handleBulkUpdateDefaultLocation}
|
|
open={bulkUpdateModal}
|
|
setOpen={setBulkUpdateModal}
|
|
isPending={isBulkUpdateDefaultLocationPending}
|
|
description={t("default_conferencing_bulk_description")}
|
|
eventTypes={eventTypes}
|
|
isEventTypesFetching={isEventTypesFetching}
|
|
handleBulkEditDialogToggle={handleBulkEditDialogToggle}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
function ConnectOrDisconnectIntegrationMenuItem(props: {
|
|
credentialId: number;
|
|
type: App["type"];
|
|
isGlobal?: boolean;
|
|
installed?: boolean;
|
|
invalidCredentialIds?: number[];
|
|
teamId?: number;
|
|
app: App["slug"];
|
|
handleDisconnect: HandleDisconnect;
|
|
handleConnectDisconnectIntegrationMenuToggle: () => void;
|
|
}) {
|
|
const { type, credentialId, isGlobal, installed, handleDisconnect, teamId, app } = props;
|
|
const { t } = useLocale();
|
|
|
|
if (credentialId || type === "stripe_payment" || isGlobal) {
|
|
return (
|
|
<DropdownMenuItem>
|
|
<DropdownItem
|
|
color="destructive"
|
|
onClick={() => handleDisconnect(credentialId, app, teamId)}
|
|
disabled={isGlobal || isDwdCredential({ credentialId })}
|
|
StartIcon="trash">
|
|
{t("remove_app")}
|
|
</DropdownItem>
|
|
</DropdownMenuItem>
|
|
);
|
|
}
|
|
|
|
if (!installed) {
|
|
return (
|
|
<div className="flex items-center truncate">
|
|
<Alert severity="warning" title={t("not_installed")} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<InstallAppButton
|
|
type={type}
|
|
render={(buttonProps) => (
|
|
<Button color="secondary" {...buttonProps} data-testid="integration-connection-button">
|
|
{t("install")}
|
|
</Button>
|
|
)}
|
|
onChanged={props.handleConnectDisconnectIntegrationMenuToggle}
|
|
/>
|
|
);
|
|
}
|