Desktop fixes: product seller + actions, collection cover, masonry skeleton

- product: show seller (logo + name) in the desktop info column (was missing
  after hiding the mobile header); action icons (save/share/collection/AI) are
  now circular bordered buttons on desktop instead of a bare icon row
- collection detail: cover constrained to a 360px portrait column with
  object-cover (fixes over-long image + side margins); tighter spacing
- masonry: only render the loading skeleton while actively fetching (removes
  the persistent grey block before the footer); sentinel moved above it

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Arda Samadi 2026-06-14 22:59:24 +03:30
parent 60d6a2a6e4
commit fa65db8804
4 changed files with 65 additions and 43 deletions

View File

@ -194,8 +194,8 @@ const MondrianProductList = ({
))}
</div>
{hasNextPage && <MondrianSkeleton isMini={true} />}
<div ref={loadMoreRef} className="h-px w-full" />
{isFetchingNextPage && <MondrianSkeleton isMini={true} />}
</>
)}
</>

View File

@ -160,53 +160,58 @@ export const ProductActions = ({ productId }: { productId: string }) => {
}
};
const actionBtn =
"flex items-center justify-center cursor-pointer transition-colors lg:w-10 lg:h-10 lg:rounded-full lg:border lg:border-WHITE3 lg:hover:bg-WHITE2";
return (
<>
<div className="flex w-full mt-4 px-4 justify-between">
<div className="flex gap-8">
{isCheckingBookmark ? (
<Skeleton className="h-6 w-6 rounded-full" />
) : isBookmarked ? (
<>
{isDeletingBookmark ? (
<div className="flex w-full mt-4 px-4 justify-between lg:mt-0 lg:px-0">
<div className="flex gap-8 lg:gap-2.5">
<button
type="button"
onClick={handleBookmark}
aria-label="ذخیره"
className={actionBtn}
>
{isCheckingBookmark ? (
<Skeleton className="h-6 w-6 rounded-full" />
) : isBookmarked ? (
isDeletingBookmark ? (
<Loader2 className="h-5 w-5 animate-spin" />
) : (
<Bookmark
size={20}
className="text-PRIMARY fill-current cursor-pointer"
onClick={handleBookmark}
/>
)}
</>
) : (
<>
{isBookmarking ? (
<Loader2 className="h-5 w-5 animate-spin" />
) : (
<BookmarkMinus
size={20}
className="cursor-pointer"
onClick={handleBookmark}
/>
)}
</>
)}
<Share2
size={20}
className="cursor-pointer"
<Bookmark size={20} className="text-PRIMARY fill-current" />
)
) : isBookmarking ? (
<Loader2 className="h-5 w-5 animate-spin" />
) : (
<BookmarkMinus size={20} />
)}
</button>
<button
type="button"
onClick={() => setIsShareDrawerOpen(true)}
/>
<Layers
size={20}
className="cursor-pointer"
aria-label="اشتراک‌گذاری"
className={actionBtn}
>
<Share2 size={20} />
</button>
<button
type="button"
onClick={handleAddToSet}
/>
aria-label="افزودن به ست"
className={actionBtn}
>
<Layers size={20} />
</button>
<div className="relative">
<Sparkles
size={20}
className="cursor-pointer animate-pulse text-PRIMARY"
<button
type="button"
onClick={handleOpenAIModal}
/>
aria-label="پرو با هوش مصنوعی"
className={actionBtn}
>
<Sparkles size={20} className="animate-pulse text-PRIMARY" />
</button>
{showAIFeatureTooltip && (
<div className="absolute bottom-full right-0 mb-2 z-50 animate-bounce">
<div className="relative bg-gradient-to-r from-purple-600 to-pink-500 text-WHITE px-3 py-2 rounded-lg shadow-xl whitespace-nowrap border border-white/20">

View File

@ -16,6 +16,7 @@ import { Button } from "../ui/button";
import { useNavigate, Link } from "@remix-run/react";
import { useSimilarProductsInfinite } from "~/requestHandler/use-product-hooks";
import MondrianProductList from "../MondrianProductList";
import SellerLogo from "../SellerLogo";
interface ProductDetailViewProps {
product: ProductDetailType;
@ -243,6 +244,22 @@ export const ProductDetailView = memo(function ProductDetailView({
{/* Info */}
<div className="flex flex-col">
{/* Seller (desktop) */}
{!isOwner && product.seller?.username ? (
<Link
to={`/seller/${product.seller.username}`}
className="hidden lg:flex items-center gap-2.5 mb-1"
>
<SellerLogo
size="sm"
src={product.seller?.storeLogo}
alt="seller"
/>
<span className="font-bold text-[15px]">
{product.seller.username}
</span>
</Link>
) : null}
{/* Inactive Product Status */}
{!productActiveStatus && isOwner ? (
<div className="flex gap-1 px-4 w-full justify-between items-center mt-2 lg:px-0">

View File

@ -141,14 +141,14 @@ export default function CollectionDetail() {
{/* Content */}
<div className="flex flex-col lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8 lg:pt-6">
{/* Collection Header */}
<div className="flex flex-col gap-4 p-4 border-b border-inner-border lg:grid lg:grid-cols-[1.3fr_1fr] lg:gap-8 lg:items-center lg:p-0 lg:py-8 lg:border-0">
<div className="flex flex-col gap-4 p-4 border-b border-inner-border lg:grid lg:grid-cols-[360px_1fr] lg:gap-8 lg:items-start lg:p-0 lg:py-6 lg:border-0">
{/* Cover Image */}
<div className="relative overflow-hidden rounded-xl w-full mx-auto h-[65vh] bg-gray-100 lg:h-auto lg:aspect-[3/4] lg:bg-WHITE2">
<div className="relative overflow-hidden rounded-xl w-full mx-auto h-[65vh] bg-gray-100 lg:h-auto lg:aspect-[3/4]">
{collection.coverImageUrl ? (
<img
src={collection.coverImageUrl}
alt={collection.title || "Collection"}
className="w-full h-full object-cover lg:object-contain"
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center bg-WHITE2">