Vitron-Front/app/routes/_index.tsx
Arda Samadi e1c5899002 Add responsive desktop layout for home page
Single responsive tree (Tailwind lg:) — mobile experience unchanged; desktop
chrome is gated to the home route for now.

- DesktopHeader / DesktopFooter components (visible only at lg+), wired to
  real routes with live cart badge and working search
- MyLayout: render desktop header/footer on home, expand container and hide
  mobile bottom nav at lg
- Home: hide mobile top bar at lg, 1320px centered container, desktop hero
  grid (carousel + 2 promos), larger section headers
- SellerTilesGrid: 2-col mobile -> 5-col desktop with name overlay

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:07:47 +03:30

326 lines
13 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, useSearchParams } from "@remix-run/react";
import { useEffect, useState } from "react";
import { ChevronLeft } from "lucide-react";
import HomePageTopBar from "~/components/home/HomePageTopBar";
import { ProductImageCarousel } from "~/components/product";
import {
useGetHomeBanners,
useGetHomeDiscounts,
useGetHomeForYou,
useGetHomeSellerTiles,
} from "~/requestHandler/use-home-hooks";
import { useCollectionsInfiniteQuery } from "~/requestHandler/use-collection-hooks";
import { Skeleton } from "~/components/ui/skeleton";
import MondrianProductList from "~/components/MondrianProductList";
import HorizontalProductList from "~/components/HorizontalProductList";
import HorizontalCollectionsList from "~/components/HorizontalCollectionsList";
import SellerTilesGrid from "~/components/SellerTilesGrid";
import InvoiceModal from "~/components/home/InvoiceModal";
export default function HomePage() {
const [searchParams] = useSearchParams();
const [showInvoiceModal, setShowInvoiceModal] = useState(false);
// Fetch home page data
const { data: banners, isLoading: isLoadingBanners } = useGetHomeBanners();
const {
data: discountsData,
isLoading: isLoadingDiscounts,
isError: isErrorDiscounts,
refetch: refetchDiscounts,
} = useGetHomeDiscounts();
const {
data: forYouData,
isLoading: isLoadingForYou,
isError: isErrorForYou,
refetch: refetchForYou,
fetchNextPage: fetchNextForYou,
hasNextPage: hasNextForYou,
isFetchingNextPage: isFetchingNextForYou,
} = useGetHomeForYou();
const {
data: sellerTilesData,
isLoading: isLoadingSellerTiles,
isError: isErrorSellerTiles,
refetch: refetchSellerTiles,
} = useGetHomeSellerTiles();
const {
data: collectionsData,
isLoading: isLoadingCollections,
isError: isErrorCollections,
refetch: refetchCollections,
} = useCollectionsInfiniteQuery({
pageSize: 10,
});
// Flatten the paginated data - extract results from each page
// For discounts, only get first page
const discounts = discountsData?.pages[0]?.results || [];
const forYouProducts =
forYouData?.pages.flatMap((page) => page?.results || []) || [];
const collections = collectionsData?.pages[0]?.results || [];
useEffect(() => {
// Check if the URL has an invoice parameter
if (searchParams.has("invoice") && searchParams.has("shippinginfo")) {
setShowInvoiceModal(true);
}
}, [searchParams]);
return (
<>
<div className="flex flex-col pb-12">
<div className="lg:hidden">
<HomePageTopBar />
</div>
{/* Adding padding to account for fixed header */}
<div className="flex flex-col gap-10 lg:gap-14 lg:max-w-[1320px] lg:w-full lg:mx-auto lg:px-8">
{/* Hero: mobile = full-bleed banner carousel; desktop = main + 2 promos */}
<div className="w-full mt-2 lg:mt-7 lg:grid lg:grid-cols-3 lg:gap-4">
<div className="h-[260px] lg:h-[408px] lg:col-span-2 lg:rounded-[22px] lg:overflow-hidden lg:shadow-md">
{isLoadingBanners ? (
<Skeleton className="w-full h-full" />
) : (
<ProductImageCarousel
images={banners?.map((banner) => banner.imageUrl || "") || []}
hideIndex={true}
borderRadius={0}
/>
)}
</div>
{/* Desktop-only side promos */}
<div className="hidden lg:grid lg:grid-rows-2 lg:gap-4">
<Link
to="/explore"
className="relative block rounded-[22px] overflow-hidden shadow-sm group"
>
<div className="absolute inset-0 bg-gradient-to-br from-[#6f7359] to-[#454935] transition-transform duration-500 group-hover:scale-105" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div className="absolute inset-x-0 bottom-0 p-5 text-white">
<small className="text-[12px] font-bold opacity-90">
تازه رسید
</small>
<h3 className="text-[20px] font-extrabold mt-1 leading-snug">
کالکشن جدید
</h3>
</div>
</Link>
<Link
to="/"
className="relative block rounded-[22px] overflow-hidden shadow-sm group"
>
<div className="absolute inset-0 bg-gradient-to-br from-[#b06a3c] to-[#7c4220] transition-transform duration-500 group-hover:scale-105" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div className="absolute inset-x-0 bottom-0 p-5 text-white">
<small className="text-[12px] font-bold opacity-90">
فروش ویژه
</small>
<h3 className="text-[20px] font-extrabold mt-1 leading-snug">
تا ۵۰٪ تخفیف
</h3>
</div>
</Link>
</div>
</div>
<section
id="section1"
className="w-full gap-2 flex flex-col max-w-[100vw] lg:max-w-none lg:gap-5"
>
<div className="w-full px-4 lg:px-0">
<p className="text-B16 lg:text-B24 font-bold">تخفیفها</p>
<p className="text-R14 lg:text-R16 text-GRAY">
فرصتهایی محدود برای انتخابهای هوشمندانه
</p>
</div>
<HorizontalProductList
products={
discounts?.map((product) => ({
id: product?.id || "",
title: product?.title || "",
images: Array.isArray(product?.imageUrls)
? product.imageUrls
: [],
discountPercent: product?.discountPercent || 0,
discountPrice: product?.discountPrice || 0,
basePrice: product?.basePrice || 0,
})) || []
}
isLoading={isLoadingDiscounts}
isError={isErrorDiscounts}
refetch={refetchDiscounts}
/>
</section>
<section
id="section2"
className="w-full gap-2 flex flex-col max-w-[100vw] lg:max-w-none lg:gap-5"
>
<div className="w-full px-4 lg:px-0 flex items-center justify-between">
<div>
<p className="text-B16 lg:text-B24 font-bold">برترین فروشگاهها</p>
<p className="text-R14 lg:text-R16 text-GRAY">
برگرفته از بالاترین سطح رضایت کاربران
</p>
</div>
<Link
to="/sellers"
className="flex items-center gap-1 text-R14 text-gray-500 hover:text-gray-800 hover:underline transition-colors group"
>
<span>مشاهده همه</span>
<ChevronLeft
size={16}
className="group-hover:-translate-x-1 transition-transform"
/>
</Link>
</div>
<SellerTilesGrid
sellers={sellerTilesData || []}
isLoading={isLoadingSellerTiles}
isError={isErrorSellerTiles}
refetch={refetchSellerTiles}
showViewAllButton={false}
/>
</section>
<section
id="collections"
className="w-full gap-2 flex flex-col max-w-[100vw] lg:max-w-none lg:gap-5"
>
<div className="w-full px-4 lg:px-0">
<p className="text-B16 lg:text-B24 font-bold">کالکشنها</p>
<p className="text-R14 lg:text-R16 text-GRAY">
مجموعههای منتخب از بهترین محصولات
</p>
</div>
<HorizontalCollectionsList
collections={
collections?.map((collection) => ({
id: collection?.id || "",
title: collection?.title || "",
description: collection?.description || "",
coverImageUrl: collection?.coverImageUrl || "",
productCount: collection?.productCount || 0,
})) || []
}
isLoading={isLoadingCollections}
isError={isErrorCollections}
refetch={refetchCollections}
showViewAllButton={true}
/>
</section>
<section
id="section3"
className="w-full gap-2 flex flex-col max-w-[100vw] lg:max-w-none lg:gap-5"
>
<div className="w-full px-4 lg:px-0">
<p className="text-B16 lg:text-B24 font-bold">برای شما</p>
<p className="text-R14 lg:text-R16 text-GRAY">
با الهام از انتخابهای شما
</p>
</div>
<MondrianProductList
products={
forYouProducts?.map((product) => ({
id: product?.id || "",
title: product?.title || "",
images: Array.isArray(product?.imageUrls)
? product.imageUrls
: [],
discountPercent: product?.discountPercent || 0,
discountPrice: product?.discountPrice || 0,
basePrice: product?.basePrice || 0,
})) || []
}
fetchNextPage={fetchNextForYou}
hasNextPage={hasNextForYou || false}
isFetchingNextPage={isFetchingNextForYou}
isLoading={isLoadingForYou}
isError={isErrorForYou}
refetch={refetchForYou}
emptyState={{}}
/>
</section>
</div>
</div>
<InvoiceModal
open={showInvoiceModal}
onOpenChange={setShowInvoiceModal}
/>
</>
);
}
export const meta: MetaFunction = () => {
const title = "ویترون | پلتفرم خرید آنلاین - محصولات باکیفیت با بهترین قیمت";
const description =
"ویترون بهترین پلتفرم خرید آنلاین در ایران. محصولات متنوع، قیمت مناسب، تحویل سریع. خرید امن از فروشگاه‌های معتبر با بالاترین کیفیت خدمات.";
const imageUrl =
"https://vitrownstatics.s3.ir-thr-at1.arvanstorage.ir/SVG-06.svg?versionId=";
const siteUrl = import.meta.env.VITE_SITE_URL || "https://vitrown.com/";
return [
{ title },
{ name: "description", content: description },
{
name: "keywords",
content:
"خرید آنلاین، فروشگاه اینترنتی، ویترون، محصولات باکیفیت، خرید امن، تحویل سریع",
},
{ name: "author", content: "Vitrown" },
{ name: "robots", content: "index, follow" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
// Open Graph
{ property: "og:type", content: "website" },
{ property: "og:title", content: title },
{ property: "og:description", content: description },
{ property: "og:image", content: imageUrl },
{ property: "og:url", content: siteUrl },
{ 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 },
// Additional SEO
{ name: "theme-color", content: "#ffffff" },
{ name: "mobile-web-app-capable", content: "yes" },
{ name: "apple-mobile-web-app-status-bar-style", content: "default" },
{ name: "format-detection", content: "telephone=no" },
// Structured Data
{
"script:ld+json": {
"@context": "https://schema.org",
"@type": "WebSite",
name: "ویترون",
description: description,
url: siteUrl,
potentialAction: {
"@type": "SearchAction",
target: {
"@type": "EntryPoint",
urlTemplate: siteUrl + "/search/{search_term_string}",
},
"query-input": "required name=search_term_string",
},
publisher: {
"@type": "Organization",
name: "ویترون",
url: siteUrl,
},
},
},
];
};