feat(home): seller brand chip (logo + name) on product tiles

Show the seller's brand on the home product cards, like the design:
a small logo circle + name overlaid above the title.

- new SellerChip component: renders nothing without a name; falls back to
  the seller's initial when no logo is present (e.g. before the backend
  ships seller_logo). seller_name is already returned today, so the name
  shows immediately.
- thread sellerName/sellerLogo/sellerUsername through ProductHomepage type,
  the home discounts + for-you mappings, and the MondrianProductList /
  HorizontalProductList card interfaces + overlays.

Only renders where seller data is mapped (home), so seller-page and
store-dashboard tiles are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Arda Samadi 2026-06-27 16:38:44 +03:30
parent 692d385d09
commit 11656c2996
5 changed files with 63 additions and 0 deletions

View File

@ -6,6 +6,7 @@ import { Skeleton } from "./ui/skeleton";
import { ErrorState } from "./UiProvider";
import discountRed from "~/assets/icons/product/discount-red.svg";
import placeholderImage from "~/assets/icons/product.png";
import SellerChip from "./SellerChip";
interface HorizontalProduct {
id?: string;
@ -14,6 +15,9 @@ interface HorizontalProduct {
discountPercent?: number;
discountPrice?: number;
basePrice?: number;
sellerName?: string;
sellerLogo?: string | null;
sellerUsername?: string;
}
const HorizontalProductList = ({
@ -138,6 +142,10 @@ const HorizontalProductList = ({
)}
{/* Overlay info */}
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 via-black/40 to-transparent p-2 pt-6 pointer-events-none">
<SellerChip
name={product.sellerName}
logo={product.sellerLogo}
/>
<div className="flex w-full justify-between gap-1">
{/* Title */}
<h3 className="text-white text-xs font-semibold line-clamp-1 mb-0.5">

View File

@ -11,6 +11,7 @@ import placeholderImage from "~/assets/icons/product.png";
import placeholderImageDark from "~/assets/icons/product-dark.png";
import { useRootData } from "~/hooks/use-root-data";
import discountRed from "~/assets/icons/product/discount-red.svg";
import SellerChip from "./SellerChip";
interface MondrianList {
id?: string;
@ -21,6 +22,9 @@ interface MondrianList {
discountPercent?: number;
discountPrice?: number;
basePrice?: number;
sellerName?: string;
sellerLogo?: string | null;
sellerUsername?: string;
}
// Utility function to detect media type
@ -400,6 +404,7 @@ const ProductCard = ({
)}
{/* Overlay info - Airbnb style */}
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 via-black/40 to-transparent p-2 pt-6 pointer-events-none">
<SellerChip name={product.sellerName} logo={product.sellerLogo} />
<div className="flex w-full justify-between gap-1">
{/* Title */}
<h3 className="text-white text-xs font-semibold line-clamp-1 mb-0.5">

View File

@ -0,0 +1,32 @@
interface SellerChipProps {
name?: string;
logo?: string | null;
}
/**
* Small brand chip (logo + name) shown over a product card's image overlay.
* Renders nothing when there's no seller name. Falls back to the seller's
* initial when no logo is available (e.g. before the backend exposes it).
*/
export default function SellerChip({ name, logo }: SellerChipProps) {
if (!name) return null;
return (
<div className="flex items-center gap-1.5 mb-1 min-w-0">
<span className="w-[15px] h-[15px] rounded-full overflow-hidden border border-white/40 bg-white/20 shrink-0 grid place-items-center text-[8px] font-bold text-white">
{logo ? (
<img
src={logo}
alt=""
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.style.display = "none";
}}
/>
) : (
name.trim().charAt(0)
)}
</span>
<span className="text-white/85 text-[10.5px] truncate">{name}</span>
</div>
);
}

View File

@ -151,6 +151,9 @@ export default function HomePage() {
discountPercent: product?.discountPercent || 0,
discountPrice: product?.discountPrice || 0,
basePrice: product?.basePrice || 0,
sellerName: product?.sellerName,
sellerLogo: product?.sellerLogo,
sellerUsername: product?.sellerUsername,
})) || []
}
isLoading={isLoadingDiscounts}
@ -235,6 +238,9 @@ export default function HomePage() {
discountPercent: product?.discountPercent || 0,
discountPrice: product?.discountPrice || 0,
basePrice: product?.basePrice || 0,
sellerName: product?.sellerName,
sellerLogo: product?.sellerLogo,
sellerUsername: product?.sellerUsername,
})) || []
}
fetchNextPage={fetchNextForYou}

View File

@ -3813,6 +3813,18 @@ export interface ProductHomepage {
* @memberof ProductHomepage
*/
readonly sellerName?: string;
/**
*
* @type {string}
* @memberof ProductHomepage
*/
readonly sellerUsername?: string;
/**
*
* @type {string}
* @memberof ProductHomepage
*/
readonly sellerLogo?: string | null;
}
/**
*