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:
parent
692d385d09
commit
11656c2996
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
32
app/components/SellerChip.tsx
Normal file
32
app/components/SellerChip.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user