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> </div>
{hasNextPage && <MondrianSkeleton isMini={true} />}
<div ref={loadMoreRef} className="h-px w-full" /> <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 ( return (
<> <>
<div className="flex w-full mt-4 px-4 justify-between"> <div className="flex w-full mt-4 px-4 justify-between lg:mt-0 lg:px-0">
<div className="flex gap-8"> <div className="flex gap-8 lg:gap-2.5">
{isCheckingBookmark ? ( <button
<Skeleton className="h-6 w-6 rounded-full" /> type="button"
) : isBookmarked ? ( onClick={handleBookmark}
<> aria-label="ذخیره"
{isDeletingBookmark ? ( className={actionBtn}
>
{isCheckingBookmark ? (
<Skeleton className="h-6 w-6 rounded-full" />
) : isBookmarked ? (
isDeletingBookmark ? (
<Loader2 className="h-5 w-5 animate-spin" /> <Loader2 className="h-5 w-5 animate-spin" />
) : ( ) : (
<Bookmark <Bookmark size={20} className="text-PRIMARY fill-current" />
size={20} )
className="text-PRIMARY fill-current cursor-pointer" ) : isBookmarking ? (
onClick={handleBookmark} <Loader2 className="h-5 w-5 animate-spin" />
/> ) : (
)} <BookmarkMinus size={20} />
</> )}
) : ( </button>
<> <button
{isBookmarking ? ( type="button"
<Loader2 className="h-5 w-5 animate-spin" />
) : (
<BookmarkMinus
size={20}
className="cursor-pointer"
onClick={handleBookmark}
/>
)}
</>
)}
<Share2
size={20}
className="cursor-pointer"
onClick={() => setIsShareDrawerOpen(true)} onClick={() => setIsShareDrawerOpen(true)}
/> aria-label="اشتراک‌گذاری"
<Layers className={actionBtn}
size={20} >
className="cursor-pointer" <Share2 size={20} />
</button>
<button
type="button"
onClick={handleAddToSet} onClick={handleAddToSet}
/> aria-label="افزودن به ست"
className={actionBtn}
>
<Layers size={20} />
</button>
<div className="relative"> <div className="relative">
<Sparkles <button
size={20} type="button"
className="cursor-pointer animate-pulse text-PRIMARY"
onClick={handleOpenAIModal} onClick={handleOpenAIModal}
/> aria-label="پرو با هوش مصنوعی"
className={actionBtn}
>
<Sparkles size={20} className="animate-pulse text-PRIMARY" />
</button>
{showAIFeatureTooltip && ( {showAIFeatureTooltip && (
<div className="absolute bottom-full right-0 mb-2 z-50 animate-bounce"> <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"> <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 { useNavigate, Link } from "@remix-run/react";
import { useSimilarProductsInfinite } from "~/requestHandler/use-product-hooks"; import { useSimilarProductsInfinite } from "~/requestHandler/use-product-hooks";
import MondrianProductList from "../MondrianProductList"; import MondrianProductList from "../MondrianProductList";
import SellerLogo from "../SellerLogo";
interface ProductDetailViewProps { interface ProductDetailViewProps {
product: ProductDetailType; product: ProductDetailType;
@ -243,6 +244,22 @@ export const ProductDetailView = memo(function ProductDetailView({
{/* Info */} {/* Info */}
<div className="flex flex-col"> <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 */} {/* Inactive Product Status */}
{!productActiveStatus && isOwner ? ( {!productActiveStatus && isOwner ? (
<div className="flex gap-1 px-4 w-full justify-between items-center mt-2 lg:px-0"> <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 */} {/* Content */}
<div className="flex flex-col lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8 lg:pt-6"> <div className="flex flex-col lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8 lg:pt-6">
{/* Collection Header */} {/* 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 */} {/* 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 ? ( {collection.coverImageUrl ? (
<img <img
src={collection.coverImageUrl} src={collection.coverImageUrl}
alt={collection.title || "Collection"} 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"> <div className="w-full h-full flex items-center justify-center bg-WHITE2">