Desktop: seller brand header redesign + collections list/detail

- seller: desktop brand header matching the design — cover, overlapping rounded
  logo, name + verified badge + @handle, horizontal stats, Follow/Message
  actions, description; mobile blocks hidden at lg; feed = responsive masonry
- /collections: 4-column card grid on desktop (was a stretched single column)
- /collection/<id>: two-column hero (cover + info) on desktop, contained width,
  mobile header hidden at lg; products use the responsive masonry
- MyLayout: desktop gate now includes /collections and /collection/*

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Arda Samadi 2026-06-14 22:00:49 +03:30
parent d0571f8253
commit cf55e16a9a
4 changed files with 128 additions and 34 deletions

View File

@ -35,6 +35,8 @@ const MyLayout = React.memo(({ children }: { children: React.ReactNode }) => {
p === "/" ||
p.startsWith("/search/") ||
p.startsWith("/seller/") ||
p === "/collections" ||
p.startsWith("/collection/") ||
p === "/cart";
// Auth pages get a full-width desktop canvas (so they can center their own
// card) but no shopping header/footer.

View File

@ -129,8 +129,8 @@ export default function CollectionDetail() {
return (
<div className="flex flex-col min-h-screen bg-WHITE pb-20">
{/* Header */}
<div className="flex flex-row h-[65px] sticky top-0 z-30 bg-WHITE w-full items-center justify-center border-b border-inner-border">
{/* Header (mobile) */}
<div className="lg:hidden flex flex-row h-[65px] sticky top-0 z-30 bg-WHITE w-full items-center justify-center border-b border-inner-border">
<ArrowRight
onClick={handleBack}
className="absolute top-5 right-5 cursor-pointer"
@ -139,11 +139,11 @@ export default function CollectionDetail() {
</div>
{/* Content */}
<div className="flex flex-col">
<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">
<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">
{/* Cover Image */}
<div className="relative overflow-hidden rounded-xl w-full mx-auto h-[65vh] bg-gray-100">
<div className="relative overflow-hidden rounded-xl w-full mx-auto h-[65vh] bg-gray-100 lg:h-[460px]">
{collection.coverImageUrl ? (
<img
src={collection.coverImageUrl}
@ -159,7 +159,7 @@ export default function CollectionDetail() {
{/* Collection Info */}
<div className="flex flex-col gap-3">
<h1 className="text-B20 font-bold text-black">
<h1 className="text-B20 lg:text-[28px] font-bold text-black">
{collection.title || "بدون عنوان"}
</h1>

View File

@ -41,7 +41,7 @@ export default function Collections() {
// Loading skeleton
const CollectionSkeleton = () => (
<div className="flex flex-col gap-4 px-4">
<div className="flex flex-col gap-4 px-4 lg:grid lg:grid-cols-4 lg:gap-5 lg:px-0">
{Array.from({ length: 6 }).map((_, index) => (
<div
key={index}
@ -117,8 +117,8 @@ export default function Collections() {
return (
<div className="flex flex-col bg-WHITE pb-20">
{/* Header - مطابق با ProfilePagesHeader */}
<div className="flex flex-row h-[65px] sticky top-0 z-30 bg-WHITE w-full items-center justify-center border-b border-inner-border">
{/* Header (mobile) */}
<div className="lg:hidden flex flex-row h-[65px] sticky top-0 z-30 bg-WHITE w-full items-center justify-center border-b border-inner-border">
<ArrowRight
onClick={handleBack}
className="absolute top-5 right-5 cursor-pointer"
@ -127,7 +127,10 @@ export default function Collections() {
</div>
{/* Content */}
<div className="flex flex-col py-6">
<div className="flex flex-col py-6 lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8">
<h1 className="hidden lg:block text-[28px] font-extrabold mb-6">
کالکشنها
</h1>
{status === "pending" ? (
<CollectionSkeleton />
) : status === "error" ? (
@ -136,7 +139,7 @@ export default function Collections() {
<EmptyState />
) : (
<>
<div className="flex flex-col gap-4 px-4">
<div className="flex flex-col gap-4 px-4 lg:grid lg:grid-cols-4 lg:gap-5 lg:px-0">
{allCollections.map((collection) => (
<Link
key={collection.id}

View File

@ -18,6 +18,7 @@ import {
Check,
UserMinus,
Shirt,
BadgeCheck,
} from "lucide-react";
import HeaderWithSupport from "../components/HeaderWithSupport";
import { useState, useEffect } from "react";
@ -192,25 +193,110 @@ export default function Seller() {
type="seller"
>
<div className="flex flex-col pb-20 lg:max-w-[1100px] lg:mx-auto lg:w-full lg:px-8 lg:pt-8">
{/* Header */}
<div className="relative">
<div className="lg:hidden">
<HeaderWithSupport
title={seller?.storeName || ""}
onBack={handleBack}
/>
{/* Desktop brand header (cover + overlapping logo + stats + actions) */}
<div className="hidden lg:block">
<div className="relative h-[200px] rounded-[22px] overflow-hidden bg-gradient-to-br from-VITROWN_BLUE to-[#14213d]">
<div className="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent" />
</div>
<div className="flex flex-col items-center justify-center gap-2 pt-2 lg:pt-0">
<div className="flex items-end gap-6 px-2 -mt-16 relative z-10">
<div className="w-[130px] h-[130px] rounded-[28px] border-[5px] border-WHITE shadow-md overflow-hidden bg-WHITE3 shrink-0">
{seller?.storeLogo ? (
<img
src={seller.storeLogo}
alt={seller?.storeName || "store"}
className="w-full h-full object-cover"
/>
) : null}
</div>
<div className="flex-1 pb-2">
<h1 className="flex items-center gap-2 text-[28px] font-extrabold">
{seller?.storeName}
{seller?.isVerified ? (
<BadgeCheck className="text-BLUE" size={22} />
) : null}
</h1>
{seller?.username ? (
<p className="text-GRAY text-sm mt-0.5 mb-3" dir="ltr">
@{seller.username}
</p>
) : (
<div className="mb-3" />
)}
<div className="flex gap-8">
<div>
<b className="block text-[20px] font-extrabold">
{seller?.productCount ?? 0}
</b>
<span className="text-GRAY text-[13px]">محصول</span>
</div>
<div>
<b className="block text-[20px] font-extrabold">
{seller?.followerCount ?? 0}
</b>
<span className="text-GRAY text-[13px]">دنبالکننده</span>
</div>
<div>
<b className="flex items-center gap-1 text-[20px] font-extrabold">
{seller?.productReviewsCount ? seller?.reviewAverage : "—"}
{seller?.productReviewsCount ? (
<Star size={16} className="fill-BLACK" />
) : null}
</b>
<span className="text-GRAY text-[13px]">امتیاز فروشگاه</span>
</div>
</div>
</div>
<div className="flex gap-2.5 pb-2">
<Button
variant="dark"
size="lg"
onClick={handleFollowToggle}
disabled={isFollowMutating || isFollowStatusLoading}
>
{isFollowed ? (
<>
<Check size={20} />
دنبال شده
</>
) : (
<>
<UserPlus size={20} />
دنبال کردن
</>
)}
</Button>
<Button
variant="outline"
size="lg"
onClick={handleCreateChatThread}
disabled={!seller?.id || isCreatingChatThread}
>
{isCreatingChatThread ? "در حال اتصال..." : "ارسال پیام"}
</Button>
</div>
</div>
{seller?.storeDescription ? (
<p className="text-BLACK2 text-[14.5px] leading-8 max-w-[760px] mt-5 px-2">
{seller.storeDescription}
</p>
) : null}
<div className="border-b border-WHITE3 mt-6" />
</div>
{/* Mobile header */}
<div className="relative lg:hidden">
<HeaderWithSupport
title={seller?.storeName || ""}
onBack={handleBack}
/>
<div className="flex flex-col items-center justify-center gap-2 pt-2">
<SellerLogo src={seller?.storeLogo} alt="store logo" />
<h1 className="hidden lg:block text-[26px] font-extrabold mt-1">
{seller?.storeName}
</h1>
</div>
</div>
<div className="w-full">
<div className="w-full p-4 lg:max-w-2xl lg:mx-auto">
<div className="flex px-4 border bg-WHITE3 rounded-[10px] w-full py-2 lg:py-4">
<div className="w-full p-4 lg:hidden">
<div className="flex px-4 border bg-WHITE3 rounded-[10px] w-full py-2">
<div className="w-full border-l flex-2 flex flex-col items-center justify-center">
<p className="text-R12">{seller?.followerCount}</p>
<p className="text-R12">دنبال کننده</p>
@ -248,14 +334,14 @@ export default function Seller() {
</div>
</div>
</div>
<div className="mt-6 px-4 lg:max-w-2xl lg:mx-auto lg:text-center">
<div className="mt-6 px-4 lg:hidden">
<ReadMoreText
sellerData={seller}
text={seller?.storeDescription || ""}
maxLength={150}
/>
</div>
<div className="mt-4 px-4 w-full flex py-4 gap-4 lg:max-w-2xl lg:mx-auto">
<div className="mt-4 px-4 w-full flex py-4 gap-4 lg:hidden">
<Button
variant="dark"
size="lg"
@ -287,14 +373,17 @@ export default function Seller() {
{isCreatingChatThread ? "در حال اتصال..." : "ارسال پیام"}
</Button>
</div>
{/* Search Bar */}
<SearchTopBar
searchValue={searchValue}
setSearchValue={setSearchValue}
placeholder="جستجو در محصولات فروشگاه..."
minimumPrice={0}
maximumPrice={10000000}
/>
{/* Search Bar (mobile) */}
<div className="lg:hidden">
<SearchTopBar
searchValue={searchValue}
setSearchValue={setSearchValue}
placeholder="جستجو در محصولات فروشگاه..."
minimumPrice={0}
maximumPrice={10000000}
/>
</div>
<div className="hidden lg:block lg:mt-2" />
{/* seller products */}
<MondrianProductList