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:
parent
60d6a2a6e4
commit
fa65db8804
@ -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} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user