diff --git a/src/components/product/badges/SearchFilterMatchBadge.tsx b/src/components/product/badges/SearchFilterMatchBadge.tsx new file mode 100644 index 00000000..51f2e1b8 --- /dev/null +++ b/src/components/product/badges/SearchFilterMatchBadge.tsx @@ -0,0 +1,36 @@ +import { Badge } from "@/components/ui/badge.tsx"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip.tsx"; +import { Filter } from "lucide-react"; +import { Link } from "@tanstack/react-router"; +import { useTranslation } from "react-i18next"; + +type Props = { + readonly filterId: string; + readonly filterName?: string; + readonly matchReason?: string; +}; + +export function SearchFilterMatchBadge({ filterId, filterName, matchReason }: Props) { + const { t } = useTranslation(); + + return ( + + + + + + {t("product.searchFilter.matchedBadge")} + + + + {(filterName ?? matchReason) && ( + + {filterName && {filterName}} + {matchReason && ( + {matchReason} + )} + + )} + + ); +} diff --git a/src/components/product/badges/__tests__/SearchFilterMatchBadge.test.tsx b/src/components/product/badges/__tests__/SearchFilterMatchBadge.test.tsx new file mode 100644 index 00000000..ff9f0930 --- /dev/null +++ b/src/components/product/badges/__tests__/SearchFilterMatchBadge.test.tsx @@ -0,0 +1,53 @@ +import type React from "react"; + +vi.mock("@tanstack/react-router", async () => { + const actual = + await vi.importActual("@tanstack/react-router"); + + return { + ...actual, + Link: ({ + to, + params, + children, + ...props + }: { + to: string; + params?: Record; + children: React.ReactNode; + }) => { + let href = to; + for (const [key, value] of Object.entries(params ?? {})) { + href = href.replace(`$${key}`, value); + } + return ( + + {children} + + ); + }, + }; +}); + +import { render, screen } from "@testing-library/react"; +import { SearchFilterMatchBadge } from "../SearchFilterMatchBadge.tsx"; + +describe("SearchFilterMatchBadge", () => { + it("should render badge with 'Treffer' text", () => { + render(); + + expect(screen.getByText("Treffer")).toBeInTheDocument(); + }); + + it("should link to the search filter page", () => { + render(); + + expect(screen.getByRole("link")).toHaveAttribute("href", "/me/search-filter/filter-123"); + }); + + it("should render the filter icon", () => { + const { container } = render(); + + expect(container.querySelector(".lucide-funnel")).toBeInTheDocument(); + }); +}); diff --git a/src/components/product/detail/ProductInfo.tsx b/src/components/product/detail/ProductInfo.tsx index eca2c3a5..825c06bb 100644 --- a/src/components/product/detail/ProductInfo.tsx +++ b/src/components/product/detail/ProductInfo.tsx @@ -22,6 +22,8 @@ export function ProductInfo({ product }: { readonly product: ProductDetail }) { const isRemoved = product.state === "REMOVED"; const merchantUrl = product.viewUrl?.href ?? product.url?.href; + const searchFilterData = product.userData?.searchFilterData; + return (
@@ -111,6 +113,28 @@ export function ProductInfo({ product }: { readonly product: ProductDetail }) { )}
+ {searchFilterData?.matched && + !searchFilterData?.hidden && + searchFilterData?.userSearchFilterId && ( +
+

+ {t("product.searchFilter.matchedBy")} +

+ + {searchFilterData.userSearchFilterName} + + {searchFilterData.matchReason && ( +

+ {searchFilterData.matchReason} +

+ )} +
+ )} +