Skip to main content

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

if (filters.minPrice !== '' && horse.PRICE < Number(filters.minPrice)) 
  return false;

if (filters.maxPrice !== '' && horse.PRICE > Number(filters.maxPrice)) 
  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

Filter Sidebar

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>

Card Information Display

1

Hero Image

Large cover photo with verified seller badge overlay
2

Horse Details

Name, breed, age, and discipline prominently displayed
3

Location

Geographic location with map pin icon
4

Pricing

Price with currency, formatted with locale-specific separators
5

Seller Info

Seller name with verification status badge

Responsive Design

The marketplace adapts seamlessly across devices:
  • 3-column grid layout
  • Sidebar always visible
  • Full filter 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} />
)}

Pagination

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.

Performance Optimizations

Initial page load is server-rendered for instant content display and SEO benefits.
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

Build docs developers (and LLMs) love