Files
calendar/apps/web/modules/webhooks/components/WebhookListItem.tsx
T
Kartik LabhshetwarGitHubKeith WilliamsDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>RomitRomit
9a5323bbb6 feat: add delete confirmation dialog to webhook list items (#26305)
* feat: add delete confirmation dialog to webhook list items

* adds appname for translation key used in alert

* extract delete wehook dialog into separate component

* add e2e test

* chore update e2e locator

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Romit <85230081+romitg2@users.noreply.github.com>
Co-authored-by: Romit <romitgabani@icloud.com>
2026-02-05 08:32:51 +05:30

222 lines
7.9 KiB
TypeScript

"use client";
import Link from "next/link";
import { useState } from "react";
import { getWebhookVersionDocsUrl, getWebhookVersionLabel } from "@calcom/features/webhooks/lib/constants";
import type { Webhook } from "@calcom/features/webhooks/lib/dto/types";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import classNames from "@calcom/ui/classNames";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import {
Dropdown,
DropdownItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@calcom/ui/components/dropdown";
import { Switch } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
import { showToast } from "@calcom/ui/components/toast";
import { Tooltip } from "@calcom/ui/components/tooltip";
import { revalidateEventTypeEditPage } from "@calcom/web/app/(use-page-wrapper)/event-types/[type]/actions";
import { revalidateWebhooksList } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions";
import { DeleteWebhookDialog } from "./dialogs/DeleteWebhookDialog";
const MAX_BADGES_TWO_ROWS = 8; // Approximately 2 rows of badges
export default function WebhookListItem(props: {
webhook: Webhook;
canEditWebhook?: boolean;
onEditWebhook: () => void;
lastItem: boolean;
permissions: {
canEditWebhook?: boolean;
canDeleteWebhook?: boolean;
};
}) {
const { t } = useLocale();
const utils = trpc.useUtils();
const { webhook } = props;
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const deleteWebhook = trpc.viewer.webhook.delete.useMutation({
async onSuccess() {
if (webhook.eventTypeId) revalidateEventTypeEditPage(webhook.eventTypeId);
revalidateWebhooksList();
showToast(t("webhook_removed_successfully"), "success");
await utils.viewer.webhook.getByViewer.invalidate();
await utils.viewer.webhook.list.invalidate();
await utils.viewer.eventTypes.get.invalidate();
setDeleteDialogOpen(false);
},
onError() {
showToast(t("something_went_wrong"), "error");
setDeleteDialogOpen(false);
},
});
const toggleWebhook = trpc.viewer.webhook.edit.useMutation({
async onSuccess(data) {
if (webhook.eventTypeId) revalidateEventTypeEditPage(webhook.eventTypeId);
revalidateWebhooksList();
showToast(t(data?.active ? "enabled" : "disabled"), "success");
await utils.viewer.webhook.getByViewer.invalidate();
await utils.viewer.webhook.list.invalidate();
await utils.viewer.eventTypes.get.invalidate();
},
});
return (
<div
data-testid="webhook-list-item"
className={classNames(
"flex w-full justify-between p-4",
props.lastItem ? "" : "border-subtle border-b"
)}>
<div className="w-full truncate">
<div className="flex">
<Tooltip content={webhook.subscriberUrl}>
<p className="text-emphasis max-w-[600px] truncate text-sm font-medium">
{webhook.subscriberUrl}
</p>
</Tooltip>
{!props.permissions.canEditWebhook && (
<Badge variant="gray" className="ml-2 ">
{t("readonly")}
</Badge>
)}
<Tooltip content={t("webhook_version")}>
<div className="flex items-center">
<Badge variant="blue" className="ml-2">
{getWebhookVersionLabel(webhook.version)}
</Badge>
</div>
</Tooltip>
<Tooltip content={t("webhook_version_docs", { version: getWebhookVersionLabel(webhook.version) })}>
<Link
href={getWebhookVersionDocsUrl(webhook.version)}
target="_blank"
className="text-subtle hover:text-emphasis ml-1 flex items-center">
<Icon name="external-link" className="h-4 w-4" />
</Link>
</Tooltip>
</div>
<Tooltip content={t("triggers_when")}>
<div className="flex w-4/5 flex-wrap">
{webhook.eventTriggers.slice(0, MAX_BADGES_TWO_ROWS).map((trigger) => (
<Badge
key={trigger}
className="mt-2.5 basis-1/5 ltr:mr-2 rtl:ml-2"
variant="gray"
startIcon="zap">
{t(`${trigger.toLowerCase()}`)}
</Badge>
))}
{webhook.eventTriggers.length > MAX_BADGES_TWO_ROWS && (
<Tooltip
content={
<div className="flex flex-col gap-1 p-1">
{webhook.eventTriggers.slice(MAX_BADGES_TWO_ROWS).map((trigger) => (
<span key={trigger} className="text-xs">
{t(`${trigger.toLowerCase()}`)}
</span>
))}
</div>
}>
<Badge className="mt-2.5 cursor-help ltr:mr-2 rtl:ml-2" variant="gray">
+{webhook.eventTriggers.length - MAX_BADGES_TWO_ROWS} {t("more")}
</Badge>
</Tooltip>
)}
</div>
</Tooltip>
</div>
{(props.permissions.canEditWebhook || props.permissions.canDeleteWebhook) && (
<div className="ml-2 flex items-center space-x-4">
<Switch
defaultChecked={webhook.active}
data-testid="webhook-switch"
disabled={!props.permissions.canEditWebhook}
onCheckedChange={(checked) =>
toggleWebhook.mutate({
id: webhook.id,
active: checked,
payloadTemplate: webhook.payloadTemplate,
eventTypeId: webhook.eventTypeId || undefined,
})
}
/>
{props.permissions.canEditWebhook && (
<Button
className="hidden lg:flex"
color="secondary"
onClick={props.onEditWebhook}
data-testid="webhook-edit-button">
{t("edit")}
</Button>
)}
{props.permissions.canDeleteWebhook && (
<Button
className="hidden lg:flex"
color="destructive"
StartIcon="trash"
variant="icon"
data-testid="delete-webhook"
onClick={() => setDeleteDialogOpen(true)}
disabled={deleteWebhook.isPending}
/>
)}
<Dropdown>
<DropdownMenuTrigger asChild>
<Button className="lg:hidden" StartIcon="ellipsis" variant="icon" color="secondary" />
</DropdownMenuTrigger>
<DropdownMenuContent>
{props.permissions.canEditWebhook && (
<DropdownMenuItem>
<DropdownItem StartIcon="pencil" color="secondary" onClick={props.onEditWebhook}>
{t("edit")}
</DropdownItem>
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
{props.permissions.canDeleteWebhook && (
<DropdownMenuItem>
<DropdownItem
StartIcon="trash"
color="destructive"
onClick={() => setDeleteDialogOpen(true)}
disabled={deleteWebhook.isPending}>
{t("delete")}
</DropdownItem>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</Dropdown>
</div>
)}
<DeleteWebhookDialog
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
isPending={deleteWebhook.isPending}
onConfirm={() => {
deleteWebhook.mutate({
id: webhook.id,
eventTypeId: webhook.eventTypeId || undefined,
teamId: webhook.teamId || undefined,
});
}}
/>
</div>
);
}