ecom / client /src /pages /store-detail.tsx
shashwatIDR's picture
Upload 147 files
b89a86e verified
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>
);
}