From d9db93911bae19b38089e53fec8c208b39793d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Fri, 24 Apr 2026 18:23:41 +0400 Subject: [PATCH] Add cookie banner deletion from overview page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the existing deleteCookieBanner GraphQL mutation to the cookie banners overview list. Inactive banners with delete permission show an action dropdown with a confirmed delete flow. Signed-off-by: Émile Ré --- .../overview/CookieBannersOverviewPage.tsx | 100 +++++++++++++++++- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/apps/console/src/pages/organizations/cookie-banners/overview/CookieBannersOverviewPage.tsx b/apps/console/src/pages/organizations/cookie-banners/overview/CookieBannersOverviewPage.tsx index 06e8f9cf3..e859ccb1e 100644 --- a/apps/console/src/pages/organizations/cookie-banners/overview/CookieBannersOverviewPage.tsx +++ b/apps/console/src/pages/organizations/cookie-banners/overview/CookieBannersOverviewPage.tsx @@ -12,13 +12,25 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. +import { formatError, type GraphQLError, sprintf } from "@probo/helpers"; import { usePageTitle } from "@probo/hooks"; import { useTranslate } from "@probo/i18n"; -import { Badge, Button, Card, PageHeader } from "@probo/ui"; -import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; +import { + ActionDropdown, + Badge, + Button, + Card, + DropdownItem, + IconTrashCan, + PageHeader, + useConfirm, + useToast, +} from "@probo/ui"; +import { type PreloadedQuery, useMutation, usePreloadedQuery } from "react-relay"; import { Link } from "react-router"; import { graphql } from "relay-runtime"; +import type { CookieBannersOverviewPageDeleteMutation } from "#/__generated__/core/CookieBannersOverviewPageDeleteMutation.graphql"; import type { CookieBannersOverviewPageQuery } from "#/__generated__/core/CookieBannersOverviewPageQuery.graphql"; import { useOrganizationId } from "#/hooks/useOrganizationId"; @@ -29,7 +41,10 @@ export const cookieBannersOverviewPageQuery = graphql` organization: node(id: $organizationId) { __typename ... on Organization { - cookieBanners(first: 50, orderBy: { field: CREATED_AT, direction: DESC }) @required(action: THROW) { + cookieBanners(first: 50, orderBy: { field: CREATED_AT, direction: DESC }) + @connection(key: "CookieBannersOverviewPage_cookieBanners", filters: []) + @required(action: THROW) { + __id edges { node { id @@ -37,6 +52,7 @@ export const cookieBannersOverviewPageQuery = graphql` origin state createdAt + canDelete: permission(action: "core:cookie-banner:delete") } } } @@ -45,6 +61,17 @@ export const cookieBannersOverviewPageQuery = graphql` } `; +const deleteCookieBannerMutation = graphql` + mutation CookieBannersOverviewPageDeleteMutation( + $input: DeleteCookieBannerInput! + $connections: [ID!]! + ) { + deleteCookieBanner(input: $input) { + deletedCookieBannerId @deleteEdge(connections: $connections) + } + } +`; + interface CookieBannersOverviewPageProps { queryRef: PreloadedQuery; } @@ -52,6 +79,8 @@ interface CookieBannersOverviewPageProps { export function CookieBannersOverviewPage({ queryRef }: CookieBannersOverviewPageProps) { const { __ } = useTranslate(); const organizationId = useOrganizationId(); + const { toast } = useToast(); + const confirm = useConfirm(); usePageTitle(__("Cookie Banners")); @@ -60,9 +89,61 @@ export function CookieBannersOverviewPage({ queryRef }: CookieBannersOverviewPag throw new Error("invalid type for node"); } + const connectionId = organization.cookieBanners.__id; const banners = organization.cookieBanners.edges.map(e => e.node); const newBannerHref = `/organizations/${organizationId}/cookie-banners/new`; + const [deleteCookieBanner] = useMutation(deleteCookieBannerMutation); + + const handleDelete = (bannerId: string, bannerName: string) => { + confirm( + () => + new Promise((resolve) => { + deleteCookieBanner({ + variables: { + input: { cookieBannerId: bannerId }, + connections: [connectionId], + }, + onCompleted(_, errors) { + if (errors?.length) { + toast({ + title: __("Error"), + description: errors[0].message, + variant: "error", + }); + } else { + toast({ + title: __("Success"), + description: __("Cookie banner deleted successfully"), + variant: "success", + }); + } + resolve(); + }, + onError(error) { + toast({ + title: __("Error"), + description: formatError( + __("Failed to delete cookie banner"), + error as GraphQLError, + ), + variant: "error", + }); + resolve(); + }, + }); + }), + { + message: sprintf( + __("This will permanently delete the cookie banner \"%s\". This action cannot be undone."), + bannerName, + ), + variant: "danger", + label: __("Delete"), + }, + ); + }; + if (banners.length === 0) { return (
@@ -111,6 +192,19 @@ export function CookieBannersOverviewPage({ queryRef }: CookieBannersOverviewPag {new Date(banner.createdAt).toLocaleDateString()} + {banner.canDelete && banner.state !== "ACTIVE" && ( +
e.preventDefault()}> + + handleDelete(banner.id, banner.name)} + variant="danger" + icon={IconTrashCan} + > + {__("Delete")} + + +
+ )}
))}