# Marketplace Home Page

> A React marketplace home page with a what and where search, category tiles, a promoted rail, new listings near you and an app band.

- Type: React block (Blocks)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.1
- Install: `npx wingo-ui@latest add marketplace-home-page`
- Docs: https://wingo-ui.com/components/marketplace-home-page

## Interaction

Type what you look for in the hero search, pick the place in the location field (it filters the towns of lib/ro-locations, accents optional, and offers All locations) and press Enter or Search: onSearch gets the text and the city id, by default the page navigates to /search?q=…&loc=…. Below 768px the two fields and the button stack and the location opens a full height sheet. The popular searches under it are links (a sideways scroll on phones). The category tiles are links to /search?cat=…; arrow keys move between them, phones show the first nine and All categories below. Promoted listings is a swipeable rail (1.3 cards on a phone, 4 on a laptop) with arrow buttons in its header on fine pointers and arrow keys when it has focus; the Promoted badge opens an explanation (a popover, a bottom sheet on phones). New listings in Cluj-Napoca: the city chip opens a searchable list of places (a sheet on phones) and the grid shows the newest listings there; a city with nothing yet shows No listings in Sibiu yet with Search every location. Hearts save a listing at once, in the rail and in the grid together; a rejected onFavoriteChange rolls the heart back. See all listings opens the results. The trust tiles and the app band (bullets, App Store and Google Play buttons, a QR code to scan on desktop) close the page. Sections rise in once when the page mounts on the client; reduced motion skips it.

## Installation

```bash
npx wingo-ui@latest add marketplace-home-page
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/marketplace-home-page` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/blocks/marketplace-home-page.tsx`
- `components/blocks/marketplace-home-page-data.ts`

npm dependencies: `npm i motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Carousel](https://wingo-ui.com/components/carousel) ([Markdown](https://wingo-ui.com/components/carousel.md))
- [Category picker](https://wingo-ui.com/components/category-picker) ([Markdown](https://wingo-ui.com/components/category-picker.md))
- [Combobox](https://wingo-ui.com/components/combobox) ([Markdown](https://wingo-ui.com/components/combobox.md))
- [Empty State](https://wingo-ui.com/components/empty-state) ([Markdown](https://wingo-ui.com/components/empty-state.md))
- [Listbox](https://wingo-ui.com/components/listbox) ([Markdown](https://wingo-ui.com/components/listbox.md))
- [Listing card](https://wingo-ui.com/components/listing-card) ([Markdown](https://wingo-ui.com/components/listing-card.md))
- [Popover](https://wingo-ui.com/components/popover) ([Markdown](https://wingo-ui.com/components/popover.md))
- [QR Code](https://wingo-ui.com/components/qr-code) ([Markdown](https://wingo-ui.com/components/qr-code.md))
- [Search Input](https://wingo-ui.com/components/search-input) ([Markdown](https://wingo-ui.com/components/search-input.md))
- [Separator](https://wingo-ui.com/components/separator) ([Markdown](https://wingo-ui.com/components/separator.md))
- [Skeleton](https://wingo-ui.com/components/skeleton) ([Markdown](https://wingo-ui.com/components/skeleton.md))
- [Tag](https://wingo-ui.com/components/tag) ([Markdown](https://wingo-ui.com/components/tag.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [useMediaQuery](https://wingo-ui.com/components/use-media-query) ([Markdown](https://wingo-ui.com/components/use-media-query.md))
- [Commerce](https://wingo-ui.com/components/commerce) ([Markdown](https://wingo-ui.com/components/commerce.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Number format](https://wingo-ui.com/components/number-format) ([Markdown](https://wingo-ui.com/components/number-format.md))
- [Relative time](https://wingo-ui.com/components/relative-time) ([Markdown](https://wingo-ui.com/components/relative-time.md))
- [Romanian locations](https://wingo-ui.com/components/ro-locations) ([Markdown](https://wingo-ui.com/components/ro-locations.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
// app/(market)/page.tsx (the shell lives in app/(market)/layout.tsx)
"use client"

import { useRouter } from "next/navigation"
import { MarketplaceHomePage } from "@/components/blocks/marketplace-home-page"

export default function HomePage() {
  const router = useRouter()
  return (
    <MarketplaceHomePage
      defaultLocation="cluj-napoca"
      onSearch={({ query, location }) => router.push(`/search?q=${encodeURIComponent(query)}&loc=${location}`)}
      onFavoriteChange={(id, favorite) => fetch(`/api/favorites/${id}`, { method: favorite ? "PUT" : "DELETE" })}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `categories?` | `MarketplaceHomeCategory[]` |  | The category tiles (MARKETPLACE_HOME_SAMPLE.categories: the 12 Northwind Market categories with lucide icons and counts). Each links to /search?cat=<id> unless it has its own href. |
| `promoted?` | `ListingData[]` |  | The promoted rail (MARKETPLACE_HOME_SAMPLE.promoted: l02, l04 and six generated promoted ads). Empty hides the section. |
| `recent?` | `ListingData[]` |  | The newest listings, newest first (MARKETPLACE_HOME_SAMPLE.recent: l01 to l08 plus 16 generated, mostly in Cluj-Napoca). The grid keeps the listings whose location starts with the chosen city. |
| `popularSearches?` | `string[]` |  | The quick searches under the hero search (MARKETPLACE_HOME_SAMPLE.popularSearches: iPhone 14, Bike, 2-bedroom apartment...). |
| `stats?` | `{ listings: number; sellers: number; cities: number }` |  | The hero subline (MARKETPLACE_HOME_SAMPLE.stats: 1,284,310 listings in 3,180 towns and villages), through labels.stats. |
| `location?` | `string` |  | The chosen city, a ro-locations id; controlled with onLocationChange. "" is every location, an unknown id reads as All locations. |
| `defaultLocation?` | `string` | `"cluj-napoca"` | The starting city of the recent section and the hero search while uncontrolled. |
| `onLocationChange?` | `(location: string) => void` |  | Every city change, from the hero field, the chip or the empty state. |
| `onSearch?` | `(query: { query: string; location: string; category: string }) => void` |  | The hero search. Default: navigates to /search?q=…&loc=… through the link provider's navigate. |
| `heroVariant?` | `"panel" \| "plain"` | `"panel"` | panel: a tinted rounded hero with a photo mosaic from 768px (flat on phones); plain: the hero sits on the page background. |
| `showPromoted?` | `boolean` | `true` | The promoted rail. |
| `showRecent?` | `boolean` | `true` | The new ads grid with the city chip. |
| `showTrust?` | `boolean` | `true` | The three trust tiles. |
| `showApp?` | `boolean` | `true` | The app band with the store buttons and the QR code. |
| `recentCount?` | `number` | `12` | Ads in the recent grid (fewer when the city has fewer). |
| `cardSize?` | `"sm" \| "md"` | `"md"` | Listing card size from 640px; phones always use the compact look in two columns. |
| `onFavoriteChange?` | `(id: string, favorite: boolean) => (void \| Promise<unknown>)` |  | A heart was toggled. The change shows at once in the rail and the grid; a rejected promise rolls it back and the card announces the error. |
| `appUrl?` | `string` | `"https://northwind.market/app"` | The QR code's value in the app band. |
| `appStoreHref?` | `string` | `"#app-store"` | The App Store button's link. |
| `googlePlayHref?` | `string` | `"#google-play"` | The Google Play button's link. |
| `categoriesHref?` | `string` | `"/categories"` | Where All categories points. |
| `deliveryFrom?` | `number` | `9.99` | The lowest courier price in the Courier delivery tile, in currency. |
| `labels?` | `Partial<MarketplaceHomePageLabels>` |  | Every visible string (DEFAULT_MARKETPLACE_HOME_PAGE_LABELS, English), counted ones as functions; nested listingCard, carousel, categoryPicker, searchInput, listbox and qrCode labels for the composed items. MARKETPLACE_HOME_PAGE_LABELS_EN is the same set under its older name. |
| `locale?` | `string` | `"en-US"` | Intl locale for the counts, prices and dates. |
| `currency?` | `string` | `"USD"` | The currency of listings without their own, and of deliveryFrom. |
| `now?` | `Date \| number` |  | The clock for the relative dates on the cards. Default: useNow() (a stable placeholder while hydrating); the demo passes MARKETPLACE_HOME_NOW. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The search button, the hero glow, the selected place, the hearts and the trust icons. |
| `radius?` | `"sm" \| "md" \| "lg"` | `"md"` | The surfaces: cards and tiles 12 / 16 / 24px, the hero and the app band 16 / 24 / 32px. |
| `loading?` | `boolean` | `false` | The hero stays; the category tiles, the promoted rail and the grid become skeletons of their real shape. |
| `animateIn?` | `boolean` | `true` | Sections rise in once when the page mounts on the client (never over server HTML, never with reduced motion). |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` |  | The router link for every link on the page. Default: the provider's (the shell's), then <a>. |
| `className?` | `string` |  | Classes for the root. |
| `classNames?` | `Partial<Record<"hero" \| "search" \| "categories" \| "promoted" \| "recent" \| "grid" \| "trust" \| "app", string>>` |  | Classes for the sections. |

## Parts

### DEFAULT_MARKETPLACE_HOME_PAGE_LABELS (constant)

The English strings for every visible label, the default of labels. Type: MarketplaceHomePageLabels.

### MARKETPLACE_HOME_PAGE_LABELS_EN (constant)

The same English strings under their older name, kept for existing imports.

### MARKETPLACE_HOME_SAMPLE (constant)

The demo data in USD: 12 categories, 8 promoted and 24 recent listings, popular searches and the hero stats.

### MARKETPLACE_HOME_SAMPLE_EN (constant)

The same sample priced in EUR, converted at USD_PER_EUR (the currency example).

### createMarketplaceHomeSample (function)

Builds the sample: ({ currency?: "USD" | "EUR" }) => MarketplaceHomeSample, with seeded generated listings.

### MARKETPLACE_HOME_NOW (constant)

The demo clock (Sep 24, 2026, 4:40 PM) as a timestamp, passed as now so relative dates stay stable.

### USD_PER_EUR (constant)

The rounded dollars per euro rate (1.09) used to convert the EUR sample.

### mulberry32 (function)

A tiny seeded random generator, (seed: number) => () => number, so the generated sample listings stay the same on every render.

### MarketplaceHomePageProps (type)

Props of MarketplaceHomePage, including the div attributes of the root.

### MarketplaceHomeCategory (type)

A category tile: id, label, icon, listing count, search keywords and an optional href.

### MarketplaceHomeSearchQuery (type)

What onSearch receives: { query: string; location: string; category: string }.

## Examples

- **Plain hero, brand actions**: The hero sits on the page background; the shell's Post a listing and the search button use the brand tone.
- **Loading**: Category tiles, the promoted rail and the grid as skeletons of their real shape.
- **A city with no listings yet**: Sibiu has nothing new: the empty state offers every location.
- **Euro prices**: en-GB formatting and the same listings priced in euro.
- **Compact, every location**: Small cards, 8 listings from every location, large radius, info tone and no trust tiles.

## See also

- [Marketplace Shell](https://wingo-ui.com/components/marketplace-shell) ([Markdown](https://wingo-ui.com/components/marketplace-shell.md))
- [Marketplace Search Page](https://wingo-ui.com/components/marketplace-search-page) ([Markdown](https://wingo-ui.com/components/marketplace-search-page.md))
- [Listing Detail Page](https://wingo-ui.com/components/listing-detail-page) ([Markdown](https://wingo-ui.com/components/listing-detail-page.md))
- [Post Listing Wizard](https://wingo-ui.com/components/post-listing-wizard) ([Markdown](https://wingo-ui.com/components/post-listing-wizard.md))
- [Category picker](https://wingo-ui.com/components/category-picker) ([Markdown](https://wingo-ui.com/components/category-picker.md))
- [Listing card](https://wingo-ui.com/components/listing-card) ([Markdown](https://wingo-ui.com/components/listing-card.md))

---

Canonical: https://wingo-ui.com/components/marketplace-home-page
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
