Vitron-Front/app/routes/sellers.tsx
Arda Samadi 660e01c3c3 feat(seller): customizable profile banner + seller page polish
- Banner editor in store edit: color swatches + custom color picker, or
  image/GIF upload with a 1600x500 dimension hint. Renders the chosen banner
  (image cover / solid color) on the public seller page, falling back to the
  default gradient.
- Sellers list: drop the desktop name overlay (match mobile, image-only tiles).
- Seller page mobile header: chat moved top-left under the banner, search+filter
  beside the follow button, followers/product counts removed (rating kept),
  compacted so products fill more of the screen, circular logo, thinner action row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 12:00:23 +03:30

144 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { MetaFunction } from "@remix-run/node";
import { Link, useNavigate } from "@remix-run/react";
import { safeGoBack } from "~/utils/helpers";
import { useGetAllSellerTiles } from "~/requestHandler/use-home-hooks";
import { Skeleton } from "~/components/ui/skeleton";
import { Store, RefreshCw, ArrowRight } from "lucide-react";
export default function Sellers() {
const navigate = useNavigate();
const { data, status, refetch } = useGetAllSellerTiles();
const allSellers = data || [];
// Loading skeleton
const SellersSkeleton = () => (
<div className="grid grid-cols-2 lg:grid-cols-5 gap-3 lg:gap-4 px-4 lg:px-0">
{Array.from({ length: 8 }).map((_, index) => (
<Skeleton key={index} className="aspect-square rounded-lg" />
))}
</div>
);
// Empty state
const EmptyState = () => (
<div className="flex flex-col items-center justify-center py-20 px-4">
<div className="w-20 h-20 rounded-full bg-WHITE2 flex items-center justify-center mb-4">
<Store size={40} className="text-GRAY" />
</div>
<h3 className="text-B18 font-bold text-black mb-2">فروشگاهی یافت نشد</h3>
<p className="text-R14 text-GRAY text-center max-w-md">
در حال حاضر فروشگاهی موجود نیست.
</p>
</div>
);
// Error state
const ErrorState = () => (
<div className="flex flex-col items-center justify-center py-20 px-4">
<div className="w-20 h-20 rounded-full bg-red-50 flex items-center justify-center mb-4">
<Store size={40} className="text-red-500" />
</div>
<h3 className="text-B18 font-bold text-black mb-2">
خطا در بارگذاری فروشگاهها
</h3>
<p className="text-R14 text-GRAY text-center max-w-md mb-6">
متاسفانه در بارگذاری فروشگاهها مشکلی پیش آمد.
</p>
<button
onClick={() => refetch()}
className="flex items-center gap-2 bg-black text-white px-6 py-3 rounded-full hover:bg-gray-800 transition-all"
>
<RefreshCw size={16} />
<span className="text-R14 font-bold">تلاش مجدد</span>
</button>
</div>
);
const handleBack = () => {
safeGoBack(navigate);
};
return (
<div className="flex flex-col bg-WHITE pb-20">
{/* 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"
/>
<p className="text-B16 font-bold">فروشگاهها</p>
</div>
{/* Content */}
<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" ? (
<SellersSkeleton />
) : status === "error" ? (
<ErrorState />
) : allSellers.length === 0 ? (
<EmptyState />
) : (
<div className="grid grid-cols-2 lg:grid-cols-5 gap-3 lg:gap-4 px-4 lg:px-0">
{allSellers.map((seller, index) => (
<Link
key={seller.id || index}
to={`/seller/${seller.sellerUsername || seller.sellerId}`}
className="relative aspect-square overflow-hidden rounded-lg group"
>
{/* Background Image */}
{seller.imageUrl ? (
<img
src={seller.imageUrl}
alt={seller.sellerName || "فروشگاه"}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
) : (
<div className="w-full h-full bg-gradient-to-br from-gray-200 to-gray-300 flex items-center justify-center">
<Store size={48} className="text-gray-400" />
</div>
)}
</Link>
))}
</div>
)}
</div>
</div>
);
}
export const meta: MetaFunction = () => {
const title = "فروشگاه‌های ویترون | برترین فروشندگان";
const description =
"مشاهده برترین فروشگاه‌های ویترون. خرید امن از فروشندگان معتبر با بالاترین کیفیت.";
const imageUrl =
"https://vitrownstatics.s3.ir-thr-at1.arvanstorage.ir/SVG-06.svg?versionId=";
return [
{ title },
{ name: "description", content: description },
{
name: "keywords",
content: "فروشگاه، فروشندگان، ویترون، خرید آنلاین، فروشگاه اینترنتی",
},
{ name: "robots", content: "index, follow" },
// Open Graph
{ property: "og:type", content: "website" },
{ property: "og:title", content: title },
{ property: "og:description", content: description },
{ property: "og:image", content: imageUrl },
{ property: "og:site_name", content: "ویترون" },
{ property: "og:locale", content: "fa_IR" },
// Twitter Card
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:title", content: title },
{ name: "twitter:description", content: description },
{ name: "twitter:image", content: imageUrl },
];
};