Spaces:
Running
Running
import { useState } from "react"; | |
import { useParams, useLocation } from "wouter"; | |
import { useQuery } from "@tanstack/react-query"; | |
import { storesApi } from "@/lib/api"; | |
import Header from "@/components/layout/header"; | |
import SellerProductGrid from "@/components/product/seller-product-grid"; | |
import { Button } from "@/components/ui/button"; | |
import { Card, CardContent } from "@/components/ui/card"; | |
import { Skeleton } from "@/components/ui/skeleton"; | |
import { LoadingSpinner } from "@/components/ui/spinner"; | |
import { ArrowLeft, Star, MapPin, User } from "lucide-react"; | |
export default function StoreDetail() { | |
const { id } = useParams(); | |
const [, setLocation] = useLocation(); | |
const [searchQuery, setSearchQuery] = useState(""); | |
const { data: storeData, isLoading, error } = useQuery({ | |
queryKey: ['/api/stores', id], | |
queryFn: () => storesApi.getById(id!), | |
enabled: !!id, | |
}); | |
if (isLoading) { | |
return ( | |
<div className="min-h-screen page-gradient"> | |
<Header searchQuery={searchQuery} setSearchQuery={setSearchQuery} /> | |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | |
<LoadingSpinner text="Loading store details..." /> | |
<div className="mt-8"> | |
<Skeleton className="h-32 w-full mb-8 rounded-2xl" /> | |
<div className="flex items-center space-x-4 mb-8"> | |
<Skeleton className="w-16 h-16 rounded-full" /> | |
<div className="flex-1"> | |
<Skeleton className="h-6 w-48 mb-2" /> | |
<Skeleton className="h-4 w-32" /> | |
</div> | |
</div> | |
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6"> | |
{[...Array(8)].map((_, i) => ( | |
<Skeleton key={i} className="aspect-square w-full rounded-2xl" /> | |
))} | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
if (error || !storeData) { | |
return ( | |
<div className="min-h-screen page-gradient"> | |
<Header searchQuery={searchQuery} setSearchQuery={setSearchQuery} /> | |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | |
<div className="text-center"> | |
<h1 className="text-2xl font-bold text-foreground mb-4">Store Not Found</h1> | |
<Button onClick={() => setLocation('/')}> | |
<ArrowLeft className="mr-2 h-4 w-4" /> | |
Back to Home | |
</Button> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
return ( | |
<div className="min-h-screen page-gradient"> | |
<Header searchQuery={searchQuery} setSearchQuery={setSearchQuery} /> | |
<div className="max-w-7xl mx-auto px-3 sm:px-4 lg:px-6 py-6" data-testid="store-detail"> | |
<Button | |
variant="ghost" | |
onClick={() => setLocation('/')} | |
className="mb-6" | |
data-testid="back-button" | |
> | |
<ArrowLeft className="mr-2 h-4 w-4" /> | |
Back to Stores | |
</Button> | |
{/* Store Banner */} | |
<div className="relative h-48 bg-gradient-to-r from-primary via-secondary to-primary rounded-2xl mb-8 overflow-hidden shadow-xl"> | |
{storeData.bannerImage ? ( | |
<img | |
src={storeData.bannerImage} | |
alt={`${storeData.name} banner`} | |
className="w-full h-full object-cover" | |
data-testid="store-banner" | |
/> | |
) : ( | |
<div className="absolute inset-0 bg-gradient-to-r from-primary via-secondary to-primary" /> | |
)} | |
<div className="absolute inset-0 bg-black/20" /> | |
<div className="absolute bottom-4 left-4 text-white"> | |
<h1 className="text-3xl font-bold" data-testid="store-name"> | |
{storeData.name} | |
</h1> | |
</div> | |
</div> | |
{/* Store Info */} | |
<Card className="mb-8 glass-card border-0"> | |
<CardContent className="p-6"> | |
<div className="flex items-start space-x-6"> | |
{/* Store Face Image */} | |
<div className="w-20 h-20 rounded-full overflow-hidden bg-gray-100 flex-shrink-0"> | |
{storeData.faceImage ? ( | |
<img | |
src={storeData.faceImage} | |
alt={`${storeData.name} profile`} | |
className="w-full h-full object-cover" | |
data-testid="store-face-image" | |
/> | |
) : ( | |
<div className="w-full h-full bg-gray-200 flex items-center justify-center"> | |
<span className="text-gray-400 text-xs">No Image</span> | |
</div> | |
)} | |
</div> | |
<div className="flex-1"> | |
<div className="flex items-center space-x-4 mb-4"> | |
<h2 className="text-xl font-bold text-foreground" data-testid="store-title"> | |
{storeData.name} | |
</h2> | |
<div className="flex items-center"> | |
<div className="flex text-yellow-400 text-sm"> | |
{[...Array(5)].map((_, i) => ( | |
<Star key={i} className="h-4 w-4 fill-current" /> | |
))} | |
</div> | |
<span className="text-sm text-muted-foreground ml-2" data-testid="store-rating"> | |
(4.9/5) | |
</span> | |
</div> | |
</div> | |
{/* Seller Info */} | |
<div className="flex items-center space-x-2 mb-4"> | |
<User className="h-4 w-4 text-primary" /> | |
<span className="text-sm font-medium text-foreground">Seller:</span> | |
<span className="text-sm text-muted-foreground" data-testid="seller-name"> | |
{storeData.seller?.username || 'Store Owner'} | |
</span> | |
</div> | |
{storeData.description && ( | |
<div className="mb-4"> | |
<h3 className="font-semibold text-foreground mb-2">About This Store</h3> | |
<p className="text-muted-foreground leading-relaxed" data-testid="store-description"> | |
{storeData.description} | |
</p> | |
</div> | |
)} | |
<div className="flex items-center text-sm text-muted-foreground"> | |
<MapPin className="h-4 w-4 mr-1" /> | |
<span>Verified Seller • Bharat</span> | |
</div> | |
</div> | |
</div> | |
</CardContent> | |
</Card> | |
{/* Store Products */} | |
<div> | |
<div className="flex items-center justify-between mb-6"> | |
<h2 className="text-2xl font-bold text-foreground" data-testid="products-title"> | |
Products from {storeData.name} | |
</h2> | |
<span className="text-muted-foreground" data-testid="products-count"> | |
{storeData.products?.length || 0} products | |
</span> | |
</div> | |
{storeData.products && storeData.products.length > 0 ? ( | |
<SellerProductGrid products={storeData.products} /> | |
) : ( | |
<div className="text-center py-12" data-testid="no-products"> | |
<p className="text-muted-foreground">This store doesn't have any products yet.</p> | |
</div> | |
)} | |
</div> | |
</div> | |
</div> | |
); | |
} | |