freshyo/apps/web-ui/src/routes/stores.$storeId.tsx
2026-07-04 15:46:18 +05:30

210 lines
7.3 KiB
TypeScript

import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { useStoreWithProducts, useAllProducts } from '../hooks/prominent-api-hooks'
import { useState, useMemo } from 'react'
import { p } from 'web-components'
import { AppLayout } from '../components/AppLayout'
import AddToCartDialog from '../components/AddToCartDialog'
import { ProductCard } from '../components/ProductCard'
import { usePopulateCentralStores } from '../hooks/usePopulateCentralStores'
import { ArrowLeft, Store, X, Grid3X3 } from 'lucide-react'
export const Route = createFileRoute('/stores/$storeId')({
component: StoreDetailPage,
})
interface Tag {
id: number
tagName: string
productIds?: number[]
}
function StoreDetailPage() {
const { storeId } = useParams({ from: '/stores/$storeId' })
const navigate = useNavigate()
const storeIdNum = Number(storeId)
const [selectedTagId, setSelectedTagId] = useState<number | null>(null)
// Populate central stores with slots and product data for out-of-stock checking
usePopulateCentralStores()
const { data: storeData, isLoading, error, refetch } = useStoreWithProducts(storeIdNum)
const { data: productsData, isLoading: isProductsLoading } = useAllProducts()
const productById = useMemo(() => {
const map = new Map<number, any>()
productsData?.products?.forEach((product) => {
map.set(product.id, product)
})
return map
}, [productsData])
const storeProducts = useMemo(() => {
if (!storeData?.products) return []
return storeData.products
.map((product: any) => productById.get(product.id))
.filter(Boolean)
}, [storeData, productById])
// Filter products based on selected tag
const filteredProducts = selectedTagId
? storeProducts.filter((product: any) => {
const selectedTag = storeData?.tags.find((t: Tag) => t.id === selectedTagId)
return selectedTag?.productIds?.includes(product.id) ?? false
})
: storeProducts
const isMeatStore = storeData?.store?.name?.toLowerCase().includes('meat')
if (isLoading || isProductsLoading) {
return (
<AppLayout>
<div className="flex min-h-screen items-center justify-center bg-gray-50">
<p className="font-medium text-gray-500">
{isLoading ? 'Loading store...' : 'Loading products...'}
</p>
</div>
</AppLayout>
)
}
if (error || !storeData) {
return (
<AppLayout>
<div className="flex min-h-screen flex-col items-center justify-center bg-gray-50">
<svg className="mb-4 h-12 w-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p className="font-bold mb-2 text-lg text-gray-900">
Oops!
</p>
<p className="text-gray-500">Store not found or error loading</p>
</div>
</AppLayout>
)
}
return (
<AppLayout>
<div className="min-h-screen bg-gray-50 pb-24">
{/* Back Button */}
<div className="sticky top-0 z-10 border-b border-gray-200 bg-white px-4 py-3">
<div className="flex items-center gap-3">
<div onClick={() => navigate({ to: '/stores' })} className="p-2">
<ArrowLeft className="h-5 w-5 text-gray-700" />
</div>
<p className="font-bold text-lg text-gray-900">
{storeData?.store?.name || 'Store'}
</p>
</div>
</div>
<div className="px-4 pt-4">
{/* Store Info Card */}
<div className="flex items-center gap-2 mb-6 rounded-2xl border border-gray-100 bg-white p-6 text-center shadow-sm">
<div className="mb-4 flex h-16 w-16 items-center justify-center self-center rounded-full bg-pink-50">
<Store className="h-7 w-7 text-brand-500" />
</div>
<p className="font-bold mb-2 text-center text-2xl text-gray-900">
{storeData?.store?.name}
</p>
{storeData?.store?.description && (
<p className="px-4 text-center leading-5 text-gray-500">
{storeData?.store?.description}
</p>
)}
</div>
{/* Tags Section */}
{storeData?.tags && storeData.tags.length > 0 && (
<div className="mb-6 flex gap-2 overflow-x-auto pb-2 scrollbar-hide">
{storeData.tags.map((tag: Tag) => (
<TagChip
key={tag.id}
tag={tag}
isSelected={selectedTagId === tag.id}
onPress={() => setSelectedTagId(selectedTagId === tag.id ? null : tag.id)}
/>
))}
</div>
)}
{/* Products Count & Clear Filter */}
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center">
<Grid3X3 className="mr-2 h-5 w-5 text-gray-700" />
<p className="font-bold text-lg text-gray-900">
{selectedTagId
? `${storeData?.tags.find((t: Tag) => t.id === selectedTagId)?.tagName} items`
: `${filteredProducts.length} products`}
</p>
</div>
{selectedTagId && (
<div
onClick={() => setSelectedTagId(null)}
className="flex items-center"
>
<p className="mr-1 text-sm font-medium text-brand-500">Clear</p>
<X className="h-4 w-4 text-brand-500" />
</div>
)}
</div>
{/* Products Grid */}
<div className="grid gap-4 sm:grid-cols-2" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))' }}>
{filteredProducts.map((product: any) => (
<ProductCard
key={product.id}
item={product}
onPress={() =>
navigate({
to: '/stores/$storeId/product/$productId',
params: { storeId, productId: String(product.id) },
})
}
showDeliveryInfo={false}
miniView={true}
useAddToCartDialog={true}
/>
))}
</div>
{filteredProducts.length === 0 && (
<div className="py-10 text-center">
<p className="font-medium text-gray-400">
{selectedTagId ? 'No products in this category' : 'No products available'}
</p>
</div>
)}
</div>
</div>
<AddToCartDialog />
</AppLayout>
)
}
interface TagChipProps {
tag: Tag
isSelected: boolean
onPress: () => void
}
function TagChip({ tag, isSelected, onPress }: TagChipProps) {
const productCount = tag.productIds?.length || 0
return (
<button
onClick={onPress}
className={`whitespace-nowrap rounded-lg border px-4 py-2 ${
isSelected
? 'border-brand-500 bg-brand-500 text-white'
: 'border-brand-500 bg-white text-brand-500'
}`}
>
<span className={`text-sm font-medium ${isSelected ? 'text-white' : 'text-brand-500'}`}>
{tag.tagName} ({productCount})
</span>
</button>
)
}