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:
parent
d0571f8253
commit
cf55e16a9a
@ -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.
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user