MyLayout only pads the bottom inset globally; each top bar must add its own top inset. Several inline page headers omitted it, so their content slid under the notch in the iOS standalone PWA (home was fine — its bar has it). Wrap the back-button headers the same way ProfilePagesHeader already does (outer sticky element carries pt-[env(safe-area-inset-top)]; inner relative bar holds the content) so the absolute back arrow stays out of the notch. Fixed: /profile hub, /discounts, /collections, /collection/:id, /sellers, seller store index, and financial transactions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
146 lines
5.5 KiB
TypeScript
146 lines
5.5 KiB
TypeScript
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 sticky top-0 z-30 bg-WHITE pt-[env(safe-area-inset-top)]">
|
||
<div className="relative flex flex-row h-[65px] 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>
|
||
</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 },
|
||
];
|
||
};
|