BigBlocks Docs

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.

View MarketTable examples →

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

PropTypeDefaultDescription
listingsMarketListing[][]Array of marketplace listings
loadingbooleanfalseShow loading state
errorstring-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
showAssetTypebooleantrueShow asset type column
showSellerbooleantrueShow seller information
maxItemsnumber-Maximum items to display
classNamestring-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

CategoryDescriptionExamples
digitalDigital assets and filesNFTs, eBooks, Software
physicalPhysical itemsElectronics, Clothing, Books
servicesService offeringsConsulting, Design, Development
collectiblesRare and unique itemsArt, Cards, Memorabilia
ticketsEvent ticketsConcerts, Sports, Theater

Purchase Flow

Transaction Process

  1. Item Selection: User clicks buy button
  2. Payment Calculation: Total cost including fees
  3. Transaction Creation: Bitcoin transaction built
  4. User Confirmation: Confirm purchase details
  5. Transaction Signing: Sign with user's private key
  6. Broadcast: Submit to BSV network
  7. 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}
/>