BigBlocks Docs
Components/Marketplace

QuickListButton

A streamlined button component for quickly creating marketplace listings with minimal configuration and instant publishing

A streamlined one-click button component for instantly creating marketplace listings with minimal configuration, perfect for adding "Sell This" functionality to any item, product card, or inventory management interface.

View Component Preview →

Installation

npx bigblocks add quick-list-button

Import

import { QuickListButton } from 'bigblocks';

Props

PropTypeRequiredDefaultDescription
namestringYes-Item name for the listing
pricenumberYes-Price in satoshis
descriptionstringNo-Optional item description
imagestringNo-Item image URL
categorystringNo'General'Marketplace category
onSuccess(listing: MarketListing) => voidNo-Success callback with created listing
onError(error: Error) => voidNo-Error callback
variant'default' | 'outline' | 'ghost'No'default'Button style variant
size'sm' | 'md' | 'lg'No'md'Button size
autoPublishbooleanNotrueAutomatically publish listing
currency'BSV' | 'USD'No'BSV'Price currency
classNamestringNo-Additional CSS classes

MarketListing Interface

interface MarketListing {
  id: string;
  name: string;
  description?: string;
  price: number;
  currency: 'BSV' | 'USD';
  category: string;
  image?: string;
  seller: {
    address: string;
    idKey: string;
    name?: string;
  };
  txid: string;
  timestamp: number;
  status: 'active' | 'pending' | 'sold';
  views: number;
  ordAddress?: string;           // For NFT listings
}

Basic Usage

import { QuickListButton } from 'bigblocks';

export default function ProductCard({ product }) {
  const handleListingSuccess = (listing: MarketListing) => {
    console.log('Product listed successfully!');
    console.log('Listing ID:', listing.id);
    console.log('Transaction:', listing.txid);
    
    // Navigate to listing page
    window.location.href = `/marketplace/listing/${listing.id}`;
  };

  return (
    <div className="product-card">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      
      <QuickListButton
        name={product.name}
        price={product.price * 100000000} // Convert to satoshis
        image={product.image}
        onSuccess={handleListingSuccess}
      />
    </div>
  );
}

Advanced Usage

Complete Inventory Management

import { QuickListButton } from 'bigblocks';
import { useState } from 'react';

interface InventoryItem {
  id: string;
  name: string;
  description: string;
  quantity: number;
  price: number;
  image: string;
  category: string;
}

export default function InventoryManagement() {
  const [inventory, setInventory] = useState<InventoryItem[]>([]);
  const [listedItems, setListedItems] = useState<Set<string>>(new Set());
  const [listings, setListings] = useState<MarketListing[]>([]);

  const handleQuickList = async (item: InventoryItem, listing: MarketListing) => {
    console.log(`Listed ${item.name} on marketplace`);
    
    // Update local state
    setListedItems(prev => new Set([...prev, item.id]));
    setListings(prev => [...prev, listing]);
    
    // Update inventory quantity
    setInventory(prev => 
      prev.map(inv => 
        inv.id === item.id 
          ? { ...inv, quantity: inv.quantity - 1 }
          : inv
      )
    );
    
    // Track analytics
    await fetch('/api/analytics/quick-list', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        itemId: item.id,
        listingId: listing.id,
        price: listing.price,
        category: listing.category
      })
    });
    
    // Show success notification
    showNotification(`${item.name} listed for ${listing.price / 100000000} BSV`);
  };

  const handleListingError = (item: InventoryItem, error: Error) => {
    console.error(`Failed to list ${item.name}:`, error);
    alert(`Failed to list item: ${error.message}`);
  };

  return (
    <div className="inventory-management">
      <h1>Inventory Management</h1>
      
      <div className="inventory-grid">
        {inventory.map((item) => (
          <div key={item.id} className="inventory-item">
            <img src={item.image} alt={item.name} />
            <h3>{item.name}</h3>
            <p className="description">{item.description}</p>
            <p className="quantity">Stock: {item.quantity}</p>
            <p className="price">${item.price}</p>
            
            <div className="actions">
              {listedItems.has(item.id) ? (
                <span className="listed-badge">✓ Listed</span>
              ) : (
                <QuickListButton
                  name={item.name}
                  price={item.price * 100000000}
                  description={item.description}
                  image={item.image}
                  category={item.category}
                  onSuccess={(listing) => handleQuickList(item, listing)}
                  onError={(error) => handleListingError(item, error)}
                  variant="outline"
                  size="sm"
                />
              )}
            </div>
          </div>
        ))}
      </div>
      
      {listings.length > 0 && (
        <div className="active-listings">
          <h2>Active Listings ({listings.length})</h2>
          <ul>
            {listings.map((listing) => (
              <li key={listing.id}>
                <a href={`/marketplace/listing/${listing.id}`}>
                  {listing.name} - {listing.price / 100000000} BSV
                </a>
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

Multi-Currency Quick Listing

import { QuickListButton } from 'bigblocks';
import { useState } from 'react';

export default function MultiCurrencyListing() {
  const [currency, setCurrency] = useState<'BSV' | 'USD'>('BSV');
  const [exchangeRate, setExchangeRate] = useState(45); // USD per BSV

  const convertPrice = (price: number, fromCurrency: 'BSV' | 'USD') => {
    if (fromCurrency === 'USD' && currency === 'BSV') {
      return Math.round((price / exchangeRate) * 100000000);
    } else if (fromCurrency === 'BSV' && currency === 'USD') {
      return Math.round((price / 100000000) * exchangeRate);
    }
    return price;
  };

  return (
    <div className="multi-currency-listing">
      <div className="currency-selector">
        <label>
          List prices in:
          <select value={currency} onChange={(e) => setCurrency(e.target.value as any)}>
            <option value="BSV">BSV</option>
            <option value="USD">USD</option>
          </select>
        </label>
      </div>
      
      <div className="products">
        {products.map((product) => (
          <div key={product.id} className="product">
            <h3>{product.name}</h3>
            <p>
              {currency === 'BSV' 
                ? `${product.priceBSV} BSV`
                : `$${product.priceUSD}`
              }
            </p>
            
            <QuickListButton
              name={product.name}
              price={currency === 'BSV' ? product.priceBSV * 100000000 : product.priceUSD}
              currency={currency}
              onSuccess={(listing) => {
                console.log(`Listed in ${currency}:`, listing);
              }}
            />
          </div>
        ))}
      </div>
    </div>
  );
}

Common Patterns

Digital Asset Quick Listing

import { QuickListButton } from 'bigblocks';

interface DigitalAsset {
  id: string;
  name: string;
  type: 'ebook' | 'music' | 'video' | 'software' | 'template';
  fileSize: string;
  preview: string;
  price: number;
}

export default function DigitalAssetStore() {
  const [assets, setAssets] = useState<DigitalAsset[]>([]);

  const getAssetIcon = (type: string) => {
    const icons = {
      ebook: '📚',
      music: '🎵',
      video: '🎬',
      software: '💻',
      template: '📄'
    };
    return icons[type] || '📁';
  };

  const handleAssetListed = async (asset: DigitalAsset, listing: MarketListing) => {
    // Store asset metadata
    await storeAssetMetadata(listing.id, {
      assetId: asset.id,
      type: asset.type,
      fileSize: asset.fileSize,
      downloadUrl: await generateSecureDownloadUrl(asset.id)
    });
    
    console.log(`Digital asset listed: ${asset.name}`);
  };

  return (
    <div className="digital-asset-store">
      <h1>Digital Assets</h1>
      
      <div className="assets-grid">
        {assets.map((asset) => (
          <div key={asset.id} className="asset-card">
            <div className="asset-icon">{getAssetIcon(asset.type)}</div>
            <h3>{asset.name}</h3>
            <p className="file-size">{asset.fileSize}</p>
            <p className="price">{asset.price / 100000000} BSV</p>
            
            <a href={asset.preview} target="_blank" className="preview-link">
              Preview
            </a>
            
            <QuickListButton
              name={asset.name}
              price={asset.price}
              description={`Digital ${asset.type} - ${asset.fileSize}`}
              category="Digital Goods"
              onSuccess={(listing) => handleAssetListed(asset, listing)}
              variant="default"
              size="sm"
            />
          </div>
        ))}
      </div>
    </div>
  );
}

NFT Collection Quick List

import { QuickListButton } from 'bigblocks';

interface NFT {
  tokenId: string;
  name: string;
  collection: string;
  rarity: string;
  image: string;
  attributes: Record<string, any>;
}

export default function NFTCollection() {
  const [nfts, setNfts] = useState<NFT[]>([]);
  const [floorPrice, setFloorPrice] = useState(100000); // satoshis

  const calculateNFTPrice = (nft: NFT) => {
    const rarityMultipliers = {
      common: 1,
      uncommon: 2,
      rare: 5,
      epic: 10,
      legendary: 25
    };
    
    return floorPrice * (rarityMultipliers[nft.rarity] || 1);
  };

  const handleNFTListing = async (nft: NFT, listing: MarketListing) => {
    console.log(`NFT listed: ${nft.name} from ${nft.collection}`);
    
    // Update on-chain metadata
    await updateNFTListingStatus(nft.tokenId, listing.id);
    
    // Refresh collection stats
    refreshCollectionStats();
  };

  return (
    <div className="nft-collection">
      <h1>My NFT Collection</h1>
      
      <div className="collection-stats">
        <p>Floor Price: {floorPrice / 100000000} BSV</p>
        <p>Total NFTs: {nfts.length}</p>
      </div>
      
      <div className="nft-grid">
        {nfts.map((nft) => (
          <div key={nft.tokenId} className="nft-item">
            <img src={nft.image} alt={nft.name} />
            <h3>{nft.name}</h3>
            <p className="collection">{nft.collection}</p>
            <p className={`rarity rarity-${nft.rarity}`}>{nft.rarity}</p>
            
            <QuickListButton
              name={nft.name}
              price={calculateNFTPrice(nft)}
              description={`${nft.collection} - ${nft.rarity}`}
              image={nft.image}
              category="NFTs"
              onSuccess={(listing) => handleNFTListing(nft, listing)}
              variant="outline"
            />
          </div>
        ))}
      </div>
    </div>
  );
}

Bulk Quick Listing

import { QuickListButton } from 'bigblocks';
import { useState } from 'react';

export default function BulkQuickListing() {
  const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
  const [bulkPrice, setBulkPrice] = useState(10000); // satoshis
  const [isListing, setIsListing] = useState(false);

  const handleBulkList = async () => {
    setIsListing(true);
    const items = Array.from(selectedItems);
    
    for (const itemId of items) {
      const item = inventory.find(i => i.id === itemId);
      if (!item) continue;
      
      // Quick list each selected item
      await new Promise((resolve) => {
        setTimeout(() => {
          console.log(`Listed: ${item.name}`);
          resolve(true);
        }, 500); // Delay to avoid rate limiting
      });
    }
    
    setIsListing(false);
    setSelectedItems(new Set());
  };

  return (
    <div className="bulk-quick-listing">
      <div className="bulk-controls">
        <h2>Bulk Quick List</h2>
        <input 
          type="number"
          value={bulkPrice / 100000000}
          onChange={(e) => setBulkPrice(parseFloat(e.target.value) * 100000000)}
          placeholder="Bulk price in BSV"
        />
        <button 
          onClick={handleBulkList}
          disabled={selectedItems.size === 0 || isListing}
        >
          List {selectedItems.size} items
        </button>
      </div>
      
      <div className="inventory-selection">
        {inventory.map((item) => (
          <div key={item.id} className="selectable-item">
            <input 
              type="checkbox"
              checked={selectedItems.has(item.id)}
              onChange={(e) => {
                if (e.target.checked) {
                  setSelectedItems(new Set([...selectedItems, item.id]));
                } else {
                  const newSet = new Set(selectedItems);
                  newSet.delete(item.id);
                  setSelectedItems(newSet);
                }
              }}
            />
            
            <span>{item.name}</span>
            
            <QuickListButton
              name={item.name}
              price={bulkPrice}
              image={item.image}
              onSuccess={(listing) => {
                console.log(`Individual quick list: ${listing.name}`);
              }}
              size="sm"
              variant="ghost"
            />
          </div>
        ))}
      </div>
    </div>
  );
}

Social Commerce Integration

import { QuickListButton } from 'bigblocks';

export default function SocialCommerce() {
  const [userPosts, setUserPosts] = useState([]);

  const handleSocialListing = async (post: any, listing: MarketListing) => {
    // Link listing to social post
    await linkListingToPost(post.id, listing.id);
    
    // Share on social feed
    await shareListingOnFeed({
      postId: post.id,
      listingId: listing.id,
      message: `Just listed "${listing.name}" for sale!`
    });
    
    // Update post with commerce tag
    setUserPosts(prev => 
      prev.map(p => 
        p.id === post.id 
          ? { ...p, hasListing: true, listingId: listing.id }
          : p
      )
    );
  };

  return (
    <div className="social-commerce">
      <h1>My Posts</h1>
      
      {userPosts.map((post) => (
        <div key={post.id} className="social-post">
          <img src={post.image} alt={post.title} />
          <h3>{post.title}</h3>
          <p>{post.description}</p>
          
          <div className="post-actions">
            <button className="like">👍 {post.likes}</button>
            <button className="share">🔄 Share</button>
            
            {!post.hasListing && (
              <QuickListButton
                name={post.title}
                price={50000} // Default price
                description={post.description}
                image={post.image}
                onSuccess={(listing) => handleSocialListing(post, listing)}
                variant="outline"
                size="sm"
              />
            )}
            
            {post.hasListing && (
              <a href={`/marketplace/listing/${post.listingId}`} className="view-listing">
                View Listing
              </a>
            )}
          </div>
        </div>
      ))}
    </div>
  );
}

Authentication Requirements

The QuickListButton requires Bitcoin authentication for creating listings:

import { 
  BitcoinAuthProvider, 
  BitcoinQueryProvider, 
  QuickListButton 
} from 'bigblocks';

function App() {
  return (
    <BitcoinQueryProvider>
      <BitcoinAuthProvider config={{ 
        apiUrl: '/api',
        walletMode: 'integrated' // Required for listing fees
      }}>
        <QuickListButton 
          name="My Item"
          price={10000}
          onSuccess={(listing) => {
            console.log('Listed:', listing);
          }}
        />
      </BitcoinAuthProvider>
    </BitcoinQueryProvider>
  );
}

API Integration

The QuickListButton uses streamlined endpoints for quick listing creation:

Quick List Endpoints

// Create quick listing
POST /api/market/quick-list
{
  name: string;
  price: number;
  description?: string;
  image?: string;
  category?: string;
  currency: 'BSV' | 'USD';
  autoPublish: boolean;
}

Response:
{
  success: boolean;
  listing: MarketListing;
  fee: number; // Listing fee in satoshis
}

// Check listing availability
GET /api/market/quick-list/check?name={name}

Response:
{
  available: boolean;
  suggestions?: string[]; // Alternative names if taken
}

Features

  • One-Click Listing: Instant marketplace listing creation
  • Minimal Configuration: Only name and price required
  • Auto-Publishing: Listings go live immediately by default
  • Multi-Currency: Support for BSV and USD pricing
  • Image Support: Optional image URL for visual listings
  • Category Assignment: Automatic or manual categorization
  • Success Callbacks: Handle post-listing actions
  • Error Management: Graceful error handling
  • Style Variants: Multiple button styles for UI flexibility

Error Handling

Common Error Scenarios

import { QuickListButton } from 'bigblocks';

export default function ErrorHandlingExample() {
  const handleListingError = (error: Error) => {
    if (error.message.includes('insufficient funds')) {
      alert('Not enough BSV for listing fee. Please add funds.');
    } else if (error.message.includes('duplicate')) {
      alert('An item with this name already exists.');
    } else if (error.message.includes('rate limit')) {
      alert('Too many listings. Please wait before creating more.');
    } else {
      alert(`Listing failed: ${error.message}`);
    }
  };

  return (
    <QuickListButton 
      name="Test Item"
      price={10000}
      onError={handleListingError}
      onSuccess={(listing) => {
        console.log('Success!', listing);
      }}
    />
  );
}

Styling

The component includes three style variants:

/* Default variant */
.quick-list-button.default {
  background: var(--primary-color);
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
}

/* Outline variant */
.quick-list-button.outline {
  background: transparent;
  border: 1px solid var(--primary-color);
  color: var(--primary-color);
}

/* Ghost variant */
.quick-list-button.ghost {
  background: transparent;
  color: var(--text-color);
  padding: 0.25rem 0.5rem;
}

/* Size variations */
.quick-list-button.sm {
  font-size: 0.875rem;
  padding: 0.25rem 0.75rem;
}

.quick-list-button.lg {
  font-size: 1.125rem;
  padding: 0.75rem 1.5rem;
}

/* Loading state */
.quick-list-button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

Performance Optimization

Debounced Listing Creation

import { QuickListButton } from 'bigblocks';
import { useCallback, useRef } from 'react';

export default function DebouncedQuickList() {
  const pendingListings = useRef(new Map());

  const debouncedList = useCallback((item: any) => {
    // Prevent duplicate listings
    if (pendingListings.current.has(item.id)) {
      return;
    }
    
    pendingListings.current.set(item.id, true);
    
    setTimeout(() => {
      pendingListings.current.delete(item.id);
    }, 5000); // 5 second cooldown
  }, []);

  return (
    <QuickListButton 
      name={item.name}
      price={item.price}
      onSuccess={(listing) => {
        debouncedList(item);
        console.log('Listed:', listing);
      }}
    />
  );
}

Troubleshooting

Common Issues

Button not responding

  • Verify Bitcoin authentication is configured
  • Check user has sufficient balance for listing fee
  • Ensure required props (name, price) are provided

Listing creation fails

  • Check for duplicate item names
  • Verify price is valid (> 0)
  • Ensure image URL is accessible
  • Check rate limiting hasn't been exceeded

Auto-publish not working

  • Confirm autoPublish prop is true (default)
  • Check user has listing permissions
  • Verify marketplace API is accessible

Success callback not firing

  • Ensure onSuccess prop is properly defined
  • Check for JavaScript errors in console
  • Verify listing was actually created

API Reference

QuickListButton Component

interface QuickListButtonProps {
  name: string;
  price: number;
  description?: string;
  image?: string;
  category?: string;
  onSuccess?: (listing: MarketListing) => void;
  onError?: (error: Error) => void;
  variant?: 'default' | 'outline' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  autoPublish?: boolean;
  currency?: 'BSV' | 'USD';
  className?: string;
}

Usage with React Query

import { useMutation } from '@tanstack/react-query';
import { QuickListButton } from 'bigblocks';

function QuickListWithQuery({ item }) {
  const listMutation = useMutation({
    mutationFn: (data: any) =>
      fetch('/api/market/quick-list', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      }).then(res => res.json()),
    onSuccess: (data) => {
      console.log('Listed via mutation:', data.listing);
      queryClient.invalidateQueries(['my-listings']);
    }
  });

  return (
    <QuickListButton 
      name={item.name}
      price={item.price}
      onSuccess={(listing) => {
        console.log('Component callback:', listing);
      }}
    />
  );
}