Overview
The Horse Trust marketplace provides a luxury horse browsing experience with advanced filtering, sorting, and real-time search capabilities. The marketplace is implemented as a server-side rendered page with client-side interactivity.
Architecture
The marketplace uses a hybrid rendering approach:
Server Component (marketplace/page.tsx)
export const dynamic = 'force-dynamic' ;
async function getHorses () {
try {
const apiUrl = process . env . NEXT_PUBLIC_API_URL ;
const res = await fetch ( ` ${ apiUrl } /horses` , {
next: { revalidate: 60 } // Revalidate every 60 seconds
});
if ( ! res . ok ) throw new Error ( 'Error al traer los caballos' );
const jsonResponse = await res . json ();
const horsesArray = Array . isArray ( jsonResponse ) ? jsonResponse : jsonResponse . data ;
if ( ! horsesArray || ! Array . isArray ( horsesArray )) return [];
return horsesArray ;
} catch ( error ) {
console . error ( "Hubo un problema conectando con el backend:" , error );
return [];
}
}
export default async function MarketplacePage () {
const initialHorses = await getHorses ();
return < MarketplaceClient initialHorses ={ initialHorses } />;
}
The marketplace uses ISR (Incremental Static Regeneration) with 60-second revalidation for optimal performance.
Client Component (MarketplaceClient.tsx)
Handles interactive filtering and sorting:
interface Horse {
ID : number ;
NAME : string ;
AGE : number ;
BREED : string ;
DISCIPLINE : string ;
LOCATION : string ;
PRICE : number ;
STATUS : string ;
SELLER_NAME : string ;
SELLER_VERIFIED : string ;
MAIN_PHOTO : string ;
}
export default function MarketplaceClient ({ initialHorses } : { initialHorses : Horse [] }) {
const [ filters , setFilters ] = useState ( defaultFilters );
const [ sortBy , setSortBy ] = useState ( 'nuevos' );
// Filter and sort logic...
}
Filtering System
The marketplace implements a comprehensive filtering engine:
Default Filters
const defaultFilters = {
minPrice: '' ,
maxPrice: '' ,
breed: 'Todas las Razas' ,
disciplines: [] as string [],
location: '' ,
verifiedOnly: false
};
Filter Logic
Price Range
Breed Filter
Discipline
Location Search
Verified Sellers
if ( filters . minPrice !== '' && horse . PRICE < Number ( filters . minPrice ))
return false ;
if ( filters . maxPrice !== '' && horse . PRICE > Number ( filters . maxPrice ))
return false ;
if (
filters . breed !== 'Todas las Razas' &&
horse . BREED . toLowerCase (). trim () !== filters . breed . toLowerCase (). trim ()
) {
return false ;
}
if ( filters . disciplines . length > 0 &&
! filters . disciplines . includes ( horse . DISCIPLINE ))
return false ;
if ( filters . location &&
! horse . LOCATION . toLowerCase (). includes ( filters . location . toLowerCase ()))
return false ;
if ( filters . verifiedOnly && horse . SELLER_VERIFIED !== 'verified' )
return false ;
Sorting Options
The marketplace supports three sorting modes:
const sortedHorses = [ ... filteredHorses ]. sort (( a , b ) => {
if ( sortBy === 'precio_asc' ) {
return a . PRICE - b . PRICE ; // Low to High
}
if ( sortBy === 'precio_desc' ) {
return b . PRICE - a . PRICE ; // High to Low
}
return b . ID - a . ID ; // Newest First (default)
});
Newest First Default sort showing latest listings first
Price: Low to High Affordable horses first
Price: High to Low Premium horses first
The FilterSideBar component provides an intuitive filtering interface:
< FilterSideBar
filters = { filters }
setFilters = { setFilters }
clearFilters = { clearFilters }
/>
Key Features
Price Range Slider : Visual price range selection
Breed Dropdown : All supported breeds
Multi-Discipline : Select multiple disciplines
Location Search : Text-based location filtering
Verified Badge : Toggle to show only verified sellers
Clear All : Reset filters with one click
Horse Card Component
Each listing is displayed using the HorseCard component:
< div className = "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6" >
{ sortedHorses . map ( horse => (
< HorseCard key = { horse . ID } horse = { horse } />
)) }
</ div >
Hero Image
Large cover photo with verified seller badge overlay
Horse Details
Name, breed, age, and discipline prominently displayed
Location
Geographic location with map pin icon
Pricing
Price with currency, formatted with locale-specific separators
Seller Info
Seller name with verification status badge
Responsive Design
The marketplace adapts seamlessly across devices:
Desktop (xl)
Tablet (md)
Mobile
3-column grid layout
Sidebar always visible
Full filter controls
2-column grid layout
Collapsible sidebar
Compact filters
Single column layout
Bottom sheet filters
Touch-optimized controls
Search Results
The marketplace displays search result counts and handles empty states:
< div className = "flex justify-between mb-8" >
< div >
< h1 className = "text-3xl font-black" > Colecciones de Lujo </ h1 >
< p className = "text-slate-500" >
{ sortedHorses . length } caballos disponibles
</ p >
</ div >
< select value = { sortBy } onChange = { ( e ) => setSortBy ( e . target . value ) } >
< option value = "nuevos" > Nuevos Ingresos </ option >
< option value = "precio_asc" > Precio: Menor a Mayor </ option >
< option value = "precio_desc" > Precio: Mayor a Menor </ option >
</ select >
</ div >
{ sortedHorses . length === 0 ? (
< div className = "text-center py-20 border-2 border-dashed" >
No se encontraron ejemplares con los filtros seleccionados.
</ div >
) : (
< HorseGrid horses = { sortedHorses } />
)}
The marketplace includes pagination controls (currently static, can be enhanced):
{ sortedHorses . length > 0 && (
< div className = "mt-12 flex items-center justify-center gap-2" >
< button className = "size-10 rounded-lg border" >
< ChevronLeft className = "w-4 h-4" />
</ button >
< button className = "size-10 bg-equestrian-navy text-white font-bold" > 1 </ button >
< button className = "size-10 rounded-lg border" > 2 </ button >
< span className = "px-2 text-slate-400" > ... </ span >
< button className = "size-10 rounded-lg border" >
< ChevronRight className = "w-4 h-4" />
</ button >
</ div >
)}
Consider implementing server-side pagination for marketplaces with 100+ listings to improve performance.
Initial page load is server-rendered for instant content display and SEO benefits.
Incremental Static Regeneration
Pages are cached and revalidated every 60 seconds, balancing freshness and speed.
All filtering and sorting happens client-side for instant feedback without network requests.
Cloudinary handles image optimization, resizing, and CDN delivery automatically.
User Experience Features
Visual Hierarchy
< main className = "mx-auto flex max-w-[1440px] px-6 py-8 lg:px-12 gap-8" >
< FilterSideBar /> { /* Left sidebar */ }
< div className = "flex-1" > { /* Main content area */ }
< Toolbar />
< HorseGrid />
< Pagination />
</ div >
</ main >
Verified Seller Badges
Verified sellers receive prominent visual indicators:
{ horse . SELLER_VERIFIED === 'verified' && (
< div className = "absolute top-4 right-4" >
< ShieldCheck className = "w-6 h-6 text-equestrian-gold" />
</ div >
)}
Accessibility
Keyboard Navigation Full keyboard support for filters and sorting
Screen Readers Semantic HTML and ARIA labels throughout
Focus Management Clear focus indicators on all interactive elements
Alt Text Descriptive alt text for all horse images
Error Handling
The marketplace gracefully handles API failures:
async function getHorses () {
try {
const res = await fetch ( ` ${ apiUrl } /horses` , {
next: { revalidate: 60 }
});
if ( ! res . ok ) throw new Error ( 'Error al traer los caballos' );
const jsonResponse = await res . json ();
return Array . isArray ( jsonResponse ) ? jsonResponse : jsonResponse . data ;
} catch ( error ) {
console . error ( "Hubo un problema conectando con el backend:" , error );
return []; // Return empty array on error
}
}
Next Steps
View Listing Details Learn about individual horse listing pages
Contact Sellers Start conversations with sellers