import DiscoverySearchBar from '@/components/discovery/DiscoverySearchBar'; import MatchCardImage from '@/components/discovery/MatchCardImage'; import MatchCardMetadata from '@/components/discovery/MatchCardMetadata'; import MatchCardPricing from '@/components/discovery/MatchCardPricing'; import { MainLayout } from '@/components/layout/MainLayout'; import Badge from '@/components/ui/Badge'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card'; import { EmptyState } from '@/components/ui/EmptyState'; import Flex from '@/components/ui/Flex'; import Grid from '@/components/ui/Grid'; import { Container } from '@/components/ui/layout'; import { LoadingState } from '@/components/ui/LoadingState'; import Stack from '@/components/ui/Stack'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/Tabs'; import { Heading, Text } from '@/components/ui/Typography'; import { useTranslation } from '@/hooks/useI18n'; import { useNavigation } from '@/hooks/useNavigation'; import { universalSearch, type DiscoveryMatch, type SearchQuery } from '@/services/discovery-api'; import { MapPin, Package, Users, Wrench } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; export default function DiscoveryPage() { const { t } = useTranslation(); const { handleFooterNavigate } = useNavigation(); const [searchParams, setSearchParams] = useSearchParams(); const [loading, setLoading] = useState(false); const [hasSearched, setHasSearched] = useState(false); const [hasActiveSearch, setHasActiveSearch] = useState(false); // Track if there's an actual search query const [activeTab, setActiveTab] = useState('all'); const [results, setResults] = useState<{ products: DiscoveryMatch[]; services: DiscoveryMatch[]; community: DiscoveryMatch[]; }>({ products: [], services: [], community: [], }); const initialQuery: SearchQuery = { query: searchParams.get('q') || undefined, categories: searchParams.get('category') ? [searchParams.get('category')!] : undefined, radius_km: searchParams.get('radius') ? parseFloat(searchParams.get('radius')!) : undefined, limit: 20, }; const handleSearch = async (query: SearchQuery) => { setLoading(true); try { const response = await universalSearch(query); setResults({ products: response.product_matches || [], services: response.service_matches || [], community: response.community_matches || [], }); setHasSearched(true); // Determine if this is an actual search (has query, category, or filters) vs just browsing const isActiveSearch = !!( query.query?.trim() || query.categories?.length || query.min_price != null || query.max_price != null || query.availability_status || query.tags?.length ); setHasActiveSearch(isActiveSearch); // Update URL params const newParams = new URLSearchParams(); if (query.query) newParams.set('q', query.query); if (query.categories?.[0]) newParams.set('category', query.categories[0]); if (query.radius_km) newParams.set('radius', query.radius_km.toString()); setSearchParams(newParams); } catch (error) { console.error('Search failed:', error); setHasSearched(true); setHasActiveSearch(false); } finally { setLoading(false); } }; useEffect(() => { // Perform initial search on page load // If query params exist, use them; otherwise perform a default browse to show available listings if (searchParams.get('q') || searchParams.get('category')) { handleSearch(initialQuery); } else { // Browse mode: show available listings without search filters // This is not a "search" so hasActiveSearch will be false handleSearch({ limit: 20 }); } }, []); const renderMatchCard = (match: DiscoveryMatch) => { const item = match.product || match.service || match.community_listing; if (!item) return null; const isProduct = !!match.product; const isService = !!match.service; const isCommunity = !!match.community_listing; // Get organization information const orgName = match.organization?.name || ''; // Get images const images = isProduct ? match.product?.Images : isService ? [] : match.community_listing?.images || []; const hasImage = images && images.length > 0; // Get item ID const itemId = isProduct ? match.product?.ID : isService ? match.service?.ID : match.community_listing?.id || Math.random().toString(); // Get category const category = isProduct ? match.product?.Category : isService ? match.service?.Type : match.community_listing?.category; // Get title const title = isProduct ? match.product?.Name : isService ? match.service?.Domain : match.community_listing?.title; // Get description const description = isProduct ? match.product?.Description : isService ? match.service?.Description : match.community_listing?.description; // Get availability status const availabilityStatus = isProduct ? match.product?.AvailabilityStatus : isService ? match.service?.AvailabilityStatus : match.community_listing?.availability_status; // Get tags const tags = isProduct ? match.product?.Tags || [] : isService ? match.service?.Tags || [] : match.community_listing?.tags || []; return ( {/* Product/Service Image */} {hasImage && }
{/* Category Badge */} {category && (
{category}
)} {/* Title */} {title} {/* Description */} {description && ( {description} )}
{hasActiveSearch && ( {t('discoveryPage.match', { score: Math.round(match.relevance_score * 100) })} )}
{/* Pricing */} {isProduct && match.product && ( )} {isService && match.service && ( )} {isCommunity && match.community_listing && ( )} {/* Metadata */}
); }; return (
{loading ? ( ) : ( {t('discoveryPage.tabs.all')} ( {results.products.length + results.services.length + results.community.length}) {t('discoveryPage.tabs.products')} ({results.products.length}) {t('discoveryPage.tabs.services')} ({results.services.length}) {t('discoveryPage.tabs.community')} ({results.community.length}) {[...results.products, ...results.services, ...results.community] .sort((a, b) => { // When browsing (no active search), don't sort by relevance score // When searching, sort by relevance score (higher = better match) if (hasActiveSearch) { return b.relevance_score - a.relevance_score; } // For browsing, maintain original order or sort by date/name return 0; }) .map((match) => renderMatchCard(match))} {hasSearched && results.products.length === 0 && results.services.length === 0 && results.community.length === 0 && ( )} {results.products.map((match) => renderMatchCard(match))} {results.products.length === 0 && ( )} {results.services.map((match) => renderMatchCard(match))} {results.services.length === 0 && ( )} {results.community.map((match) => renderMatchCard(match))} {results.community.length === 0 && ( )} )}
); }