MarketTable
A full-featured table component for displaying marketplace listings with sorting, filtering, and action capabilities.
MarketTable
A full-featured table component for displaying marketplace listings with sorting, filtering, and action capabilities. Perfect for building Bitcoin SV marketplaces with comprehensive listing management.
Installation
npm install bigblocks
Usage
import { MarketTable } from 'bigblocks';
export default function MarketplacePage() {
const listings = [
{
id: '1',
title: 'Bitcoin T-Shirt',
price: { bsv: '0.01', satoshis: 1000000 },
seller: { name: 'CryptoStore', rating: 4.8 },
category: 'clothing',
status: 'active'
}
];
return (
<MarketTable
listings={listings}
onListingClick={(listing) => router.push(`/listing/${listing.id}`)}
onBuySuccess={(txid, listing) => {
console.log('Purchase successful:', txid);
}}
/>
);
}
Props
Prop | Type | Default | Description |
---|---|---|---|
listings | MarketListing[] | [] | Array of marketplace listings |
loading | boolean | false | Show loading state |
error | string | - | Error message to display |
onListingClick | (listing: MarketListing) => void | - | Callback when listing is clicked |
onBuySuccess | (txid: string, listing: MarketListing) => void | - | Success callback for purchases |
onBuyError | (error: Error, listing: MarketListing) => void | - | Error callback for purchases |
showAssetType | boolean | true | Show asset type column |
showSeller | boolean | true | Show seller information |
maxItems | number | - | Maximum items to display |
className | string | - | Additional CSS classes |
MarketListing Interface
interface MarketListing {
id: string;
title: string;
description?: string;
price: {
satoshis: number;
bsv: string;
usd?: number;
};
seller: {
name: string;
address?: string;
rating?: number;
verified?: boolean;
};
category: string;
condition?: 'new' | 'used' | 'refurbished';
images?: Array<{
url: string;
thumbnailUrl?: string;
}>;
status: 'active' | 'sold' | 'reserved' | 'expired';
created: number;
expires?: number;
}
Features
- Responsive Design: Mobile-optimized table layout
- Interactive Actions: Buy buttons with transaction handling
- Seller Information: Ratings, verification status, and profiles
- Asset Categories: Flexible categorization system
- Real-time Updates: Live listing status and price changes
- Error Handling: Comprehensive error states and recovery
- Loading States: Skeleton loading for better UX
Examples
Basic Market Table
<MarketTable
listings={listings}
onListingClick={(listing) => {
console.log('Viewing listing:', listing.title);
}}
/>
With Purchase Handling
<MarketTable
listings={listings}
onBuySuccess={(txid, listing) => {
toast.success(`Purchased ${listing.title}! TX: ${txid}`);
refreshListings();
}}
onBuyError={(error, listing) => {
toast.error(`Failed to buy ${listing.title}: ${error.message}`);
}}
/>
Loading State
<MarketTable
listings={[]}
loading={true}
onListingClick={handleListingClick}
/>
Error State
<MarketTable
listings={[]}
error="Failed to load marketplace listings"
onListingClick={handleListingClick}
/>
Compact Version
import { CompactMarketTable } from 'bigblocks';
<CompactMarketTable
listings={featuredListings}
maxItems={5}
className="featured-items"
/>
Filtered Listings
function FilteredMarketplace() {
const [category, setCategory] = useState('all');
const [priceRange, setPriceRange] = useState({ min: 0, max: 1000000 });
const [filteredListings, setFilteredListings] = useState([]);
const applyFilters = useCallback(() => {
let filtered = allListings;
if (category !== 'all') {
filtered = filtered.filter(listing => listing.category === category);
}
filtered = filtered.filter(listing =>
listing.price.satoshis >= priceRange.min &&
listing.price.satoshis <= priceRange.max
);
setFilteredListings(filtered);
}, [category, priceRange, allListings]);
return (
<div className="filtered-marketplace">
<div className="filters">
<select value={category} onChange={(e) => setCategory(e.target.value)}>
<option value="all">All Categories</option>
<option value="digital">Digital Assets</option>
<option value="physical">Physical Items</option>
<option value="services">Services</option>
</select>
<div className="price-range">
<input
type="number"
placeholder="Min Price (sats)"
value={priceRange.min}
onChange={(e) => setPriceRange(prev => ({
...prev,
min: parseInt(e.target.value) || 0
}))}
/>
<input
type="number"
placeholder="Max Price (sats)"
value={priceRange.max}
onChange={(e) => setPriceRange(prev => ({
...prev,
max: parseInt(e.target.value) || 1000000
}))}
/>
</div>
</div>
<MarketTable
listings={filteredListings}
onListingClick={handleListingClick}
onBuySuccess={handlePurchaseSuccess}
/>
</div>
);
}
Marketplace Dashboard
function MarketplaceDashboard() {
const [listings, setListings] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [stats, setStats] = useState(null);
useEffect(() => {
fetchMarketData()
.then(data => {
setListings(data.listings);
setStats(data.stats);
})
.catch(setError)
.finally(() => setLoading(false));
}, []);
return (
<div className="marketplace-dashboard">
<div className="dashboard-header">
<h1>Marketplace</h1>
{stats && (
<div className="stats">
<div className="stat">
<span className="value">{stats.totalListings}</span>
<span className="label">Active Listings</span>
</div>
<div className="stat">
<span className="value">{stats.totalVolume}</span>
<span className="label">Volume (BSV)</span>
</div>
</div>
)}
</div>
<MarketTable
listings={listings}
loading={loading}
error={error}
onListingClick={(listing) => {
router.push(`/marketplace/listing/${listing.id}`);
}}
onBuySuccess={(txid, listing) => {
toast.success('Purchase completed successfully!');
// Update local state
setListings(prev =>
prev.map(l =>
l.id === listing.id
? { ...l, status: 'sold' }
: l
)
);
}}
className="main-market-table"
/>
</div>
);
}
Seller's Store View
function SellerStore({ sellerId }) {
const [sellerListings, setSellerListings] = useState([]);
const [sellerInfo, setSellerInfo] = useState(null);
useEffect(() => {
fetchSellerData(sellerId).then(data => {
setSellerListings(data.listings);
setSellerInfo(data.seller);
});
}, [sellerId]);
return (
<div className="seller-store">
{sellerInfo && (
<div className="seller-header">
<img src={sellerInfo.avatar} alt={sellerInfo.name} />
<div className="seller-details">
<h2>{sellerInfo.name}</h2>
<div className="rating">
⭐ {sellerInfo.rating} ({sellerInfo.totalSales} sales)
</div>
{sellerInfo.verified && (
<span className="verified-badge">✓ Verified</span>
)}
</div>
</div>
)}
<MarketTable
listings={sellerListings}
showSeller={false} // Hide seller column since all items are from same seller
onListingClick={(listing) => {
router.push(`/listing/${listing.id}`);
}}
onBuySuccess={(txid, listing) => {
console.log('Purchased from seller:', txid);
}}
/>
</div>
);
}
Real-time Market Updates
function LiveMarketTable() {
const [listings, setListings] = useState([]);
useEffect(() => {
// WebSocket for real-time updates
const ws = new WebSocket('/api/market/stream');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'listing_created':
setListings(prev => [data.listing, ...prev]);
break;
case 'listing_updated':
setListings(prev =>
prev.map(listing =>
listing.id === data.listingId
? { ...listing, ...data.updates }
: listing
)
);
break;
case 'listing_sold':
setListings(prev =>
prev.map(listing =>
listing.id === data.listingId
? { ...listing, status: 'sold' }
: listing
)
);
break;
}
};
return () => ws.close();
}, []);
return (
<MarketTable
listings={listings}
onListingClick={handleListingClick}
onBuySuccess={handlePurchaseSuccess}
/>
);
}
Required Context
The component requires the following providers:
import {
BitcoinAuthProvider,
BitcoinQueryProvider
} from 'bigblocks';
function App() {
return (
<BitcoinAuthProvider>
<BitcoinQueryProvider>
<MarketTable
listings={listings}
onBuySuccess={handlePurchaseSuccess}
/>
</BitcoinQueryProvider>
</BitcoinAuthProvider>
);
}
API Integration
Required Backend Endpoints
The component expects these API endpoints:
1. Get Market Listings
GET /api/market/listings?category=<category>&sort=<sort>&limit=20
Response:
{
listings: MarketListing[];
pagination: {
total: number;
hasMore: boolean;
};
filters: {
categories: string[];
priceRanges: Array<{ min: number; max: number; count: number }>;
};
}
2. Purchase Listing
POST /api/market/listings/{id}/purchase
Request:
{
quantity: number;
shippingAddress?: string;
paymentMethod: 'bsv';
}
Response:
{
success: boolean;
txid: string;
orderId: string;
totalCost: number;
}
3. Get Listing Details
GET /api/market/listings/{id}
Response:
{
listing: MarketListing;
seller: SellerProfile;
related: MarketListing[];
}
Authentication Headers
For authenticated market operations:
headers: {
'X-Auth-Token': '<BSM signature>',
'Content-Type': 'application/json'
}
Market Categories
Supported Categories
Category | Description | Examples |
---|---|---|
digital | Digital assets and files | NFTs, eBooks, Software |
physical | Physical items | Electronics, Clothing, Books |
services | Service offerings | Consulting, Design, Development |
collectibles | Rare and unique items | Art, Cards, Memorabilia |
tickets | Event tickets | Concerts, Sports, Theater |
Purchase Flow
Transaction Process
- Item Selection: User clicks buy button
- Payment Calculation: Total cost including fees
- Transaction Creation: Bitcoin transaction built
- User Confirmation: Confirm purchase details
- Transaction Signing: Sign with user's private key
- Broadcast: Submit to BSV network
- Confirmation: Monitor transaction status
Error Handling
<MarketTable
listings={listings}
onBuyError={(error, listing) => {
switch (error.message) {
case 'INSUFFICIENT_FUNDS':
toast.error('Insufficient BSV balance');
break;
case 'LISTING_UNAVAILABLE':
toast.error('Item no longer available');
break;
case 'SELLER_OFFLINE':
toast.error('Seller is currently offline');
break;
default:
toast.error(`Purchase failed: ${error.message}`);
}
}}
/>
Performance Considerations
- Lazy Loading: Load images on demand
- Pagination: Handle large datasets efficiently
- Caching: Cache listing data for better performance
- Debounced Search: Prevent excessive API calls
- Virtual Scrolling: For very large lists
Styling Customization
// Custom marketplace theme
<MarketTable
listings={listings}
className="
bg-white dark:bg-gray-900
border border-gray-200 dark:border-gray-700
rounded-lg shadow-lg
"
onListingClick={handleClick}
/>
// Compact mobile view
<MarketTable
listings={listings}
className="md:hidden compact-mobile-table"
maxItems={10}
/>
Related Components
- CompactMarketTable - Space-efficient display
- BuyListingButton - Individual purchase buttons
- CreateListingButton - Create new listings
- QuickBuyButton - Simplified purchase flow