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.
Installation
npx bigblocks add quick-list-button
Import
import { QuickListButton } from 'bigblocks';
Props
Prop | Type | Required | Default | Description |
---|---|---|---|---|
name | string | Yes | - | Item name for the listing |
price | number | Yes | - | Price in satoshis |
description | string | No | - | Optional item description |
image | string | No | - | Item image URL |
category | string | No | 'General' | Marketplace category |
onSuccess | (listing: MarketListing) => void | No | - | Success callback with created listing |
onError | (error: Error) => void | No | - | Error callback |
variant | 'default' | 'outline' | 'ghost' | No | 'default' | Button style variant |
size | 'sm' | 'md' | 'lg' | No | 'md' | Button size |
autoPublish | boolean | No | true | Automatically publish listing |
currency | 'BSV' | 'USD' | No | 'BSV' | Price currency |
className | string | No | - | 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
Related Components
- CreateListingButton - Full listing creation interface
- QuickBuyButton - One-click purchase button
- CompactMarketTable - Display marketplace listings
- MarketTable - Full marketplace table
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);
}}
/>
);
}