cosmic-nft-bazaar / script.js
sudo-saidso's picture
okay everything is dead no workie
28795df verified
// Configuration
const RARIBLE_API_KEY = 'd01c6b48-8f7a-4f58-8a1e-5c5a5d9c5b9a'; // New test API key
const RARIBLE_API_BASE = 'https://api.rarible.org/v0.1';
const CONTRACT_ADDRESS = 'ETHEREUM:0x60f80121c31a0d46b5279700f9df786054aa5ee5'; // New test contract
// State
let state = {
walletAddress: null,
nfts: [],
filteredNFTs: [],
isLoading: false,
currentFilter: 'all'
};
// DOM Elements
const elements = {
connectWalletBtn: document.getElementById('connectWallet'),
disconnectWalletBtn: document.getElementById('disconnectWallet'),
walletAddressEl: document.getElementById('walletAddress'),
walletInfoEl: document.getElementById('walletInfo'),
nftContainer: document.getElementById('nftContainer'),
searchInput: document.getElementById('searchInput'),
filterButtons: document.querySelectorAll('.filter-btn'),
refreshBtn: document.getElementById('refreshNFTs'),
statusMessage: document.getElementById('statusMessage')
};
// Initialize
document.addEventListener('DOMContentLoaded', async () => {
await checkWalletConnection();
setupEventListeners();
});
// Check if wallet is already connected
async function checkWalletConnection() {
if (typeof window.ethereum === 'undefined') {
window.demoMode = true;
showStatus('No wallet detected - using demo mode', 'info');
return;
}
try {
const accounts = await window.ethereum.request({ method: 'eth_accounts' });
if (accounts.length > 0) {
state.walletAddress = accounts[0];
updateWalletDisplay();
showStatus('Wallet connected! Click Refresh to load NFTs.', 'success');
} else {
window.demoMode = true;
showStatus('Connect wallet or use demo mode', 'info');
}
} catch (error) {
console.error('Error checking wallet:', error);
window.demoMode = true;
showStatus('Using demo mode', 'info');
}
}
// Connect Wallet
elements.connectWalletBtn.addEventListener('click', async () => {
if (typeof window.ethereum === 'undefined') {
window.demoMode = true;
showStatus('No wallet detected - using demo mode', 'info');
fetchNFTs();
return;
}
try {
elements.connectWalletBtn.disabled = true;
elements.connectWalletBtn.textContent = 'Connecting...';
showStatus('Connecting to wallet...', 'info');
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
if (accounts.length > 0) {
state.walletAddress = accounts[0];
window.demoMode = false;
updateWalletDisplay();
showStatus('Wallet connected! Click Refresh to load NFTs.', 'success');
window.ethereum.on('accountsChanged', handleAccountsChanged);
} else {
window.demoMode = true;
showStatus('Using demo mode', 'info');
}
} catch (error) {
console.error('Error connecting wallet:', error);
window.demoMode = true;
showStatus('Using demo mode', 'info');
fetchNFTs();
} finally {
elements.connectWalletBtn.disabled = false;
elements.connectWalletBtn.textContent = 'Connect Wallet';
}
});
// Disconnect Wallet
elements.disconnectWalletBtn.addEventListener('click', () => {
disconnectWallet();
});
// Handle account changes
function handleAccountsChanged(accounts) {
if (accounts.length === 0) {
disconnectWallet();
showStatus('Wallet disconnected', 'info');
} else {
state.walletAddress = accounts[0];
updateWalletDisplay();
showStatus('Wallet account changed', 'info');
// Optionally reload NFTs
fetchNFTs();
}
}
// Disconnect wallet function
function disconnectWallet() {
state.walletAddress = null;
state.nfts = [];
state.filteredNFTs = [];
window.demoMode = true;
if (window.ethereum?.removeListener) {
window.ethereum.removeListener('accountsChanged', handleAccountsChanged);
}
elements.walletInfoEl.style.display = 'none';
elements.connectWalletBtn.style.display = 'block';
elements.connectWalletBtn.disabled = false;
fetchNFTs(); // Will fall back to demo mode
showStatus('Wallet disconnected - using demo mode', 'info');
}
// Update wallet display
function updateWalletDisplay() {
if (state.walletAddress) {
const shortAddress = `${state.walletAddress.substring(0, 6)}...${state.walletAddress.substring(38)}`;
elements.walletAddressEl.textContent = shortAddress;
elements.walletInfoEl.style.display = 'flex';
elements.connectWalletBtn.style.display = 'none';
}
}
// Show status message
function showStatus(message, type = 'info') {
elements.statusMessage.textContent = message;
elements.statusMessage.className = `status-message ${type}`;
elements.statusMessage.style.display = 'block';
// Auto-hide after 5 seconds
setTimeout(() => {
elements.statusMessage.style.display = 'none';
}, 5000);
}
// Fetch NFTs from Rarible API
async function fetchNFTs() {
if (state.isLoading) {
showStatus('Already loading NFTs...', 'info');
return;
}
if (!state.walletAddress && !window.demoMode) {
showStatus('Please connect your wallet first', 'error');
return;
}
try {
state.isLoading = true;
elements.refreshBtn.disabled = true;
elements.refreshBtn.textContent = 'Loading...';
showStatus('Loading NFTs...', 'info');
renderLoadingState();
if (window.demoMode) {
// Use demo data
const demoNFTs = [
{
id: 'demo1',
meta: {
name: 'Cosmic Explorer #1',
description: 'A demo NFT for testing purposes',
image: 'http://static.photos/abstract/640x360/1'
},
sellOrders: [{
id: 'order1',
take: {
value: '1000000000000000000', // 1 ETH
type: { '@type': 'ETH', decimals: 18 }
}
}]
},
{
id: 'demo2',
meta: {
name: 'Stellar Artwork',
description: 'Another demo NFT for testing',
image: 'http://static.photos/abstract/640x360/2'
},
sellOrders: []
},
{
id: 'demo3',
meta: {
name: 'Quantum Pixel',
description: 'Digital art piece',
image: 'http://static.photos/abstract/640x360/3'
},
sellOrders: [{
id: 'order3',
take: {
value: '250000000000000000', // 0.25 ETH
type: { '@type': 'ETH', decimals: 18 }
}
}]
}
];
state.nfts = demoNFTs;
state.filteredNFTs = [...demoNFTs];
applyFilter(state.currentFilter);
showStatus('Demo mode: Loaded 3 test NFTs', 'success');
return;
}
// Real API fetch
const encodedWallet = encodeURIComponent(`ETHEREUM:${state.walletAddress}`);
const apiUrl = `${RARIBLE_API_BASE}/items/byOwner?owner=${encodedWallet}&size=50`;
const response = await fetch(apiUrl, {
headers: {
'X-API-KEY': RARIBLE_API_KEY,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
state.nfts = data.items || [];
state.filteredNFTs = [...state.nfts];
if (state.nfts.length === 0) {
renderEmptyState('No NFTs found in your wallet');
showStatus('No NFTs found in your wallet', 'info');
return;
}
applyFilter(state.currentFilter);
showStatus(`Loaded ${state.nfts.length} NFTs`, 'success');
} catch (error) {
console.error('Error:', error);
window.demoMode = true;
fetchNFTs(); // Fallback to demo mode
} finally {
state.isLoading = false;
elements.refreshBtn.disabled = false;
elements.refreshBtn.textContent = '🔄 Refresh NFTs';
}
}
// Enrich NFT with order data
async function enrichNFTWithOrderData(nft) {
try {
const itemId = encodeURIComponent(nft.id);
// Get sell orders for this NFT
const ordersResponse = await fetch(
`${RARIBLE_API_BASE}/orders/items/${itemId}/sell`,
{
headers: {
'X-API-KEY': RARIBLE_API_KEY,
'Accept': 'application/json'
}
}
);
if (ordersResponse.ok) {
const ordersData = await ordersResponse.json();
nft.sellOrders = (ordersData.orders || []).filter(order =>
order.status === 'ACTIVE' &&
order.makeStock > 0 // Ensure there's available stock
);
// Get best price (lowest)
if (nft.sellOrders.length > 0) {
nft.bestOrder = nft.sellOrders.reduce((best, current) => {
const bestPrice = best ? parseFloat(best.take.value) : Infinity;
const currentPrice = current ? parseFloat(current.take.value) : Infinity;
return currentPrice < bestPrice ? current : best;
});
}
} else {
nft.sellOrders = [];
nft.bestOrder = null;
}
} catch (error) {
console.error('Error enriching NFT:', nft.id, error);
nft.sellOrders = [];
nft.bestOrder = null;
}
}
// Render NFTs without flickering
function renderNFTs() {
if (!elements.nftContainer) return;
if (state.filteredNFTs.length === 0) {
renderEmptyState('No NFTs match your filter');
return;
}
// Clear and render in one operation to prevent flickering
const fragment = document.createDocumentFragment();
state.filteredNFTs.forEach((nft, index) => {
const card = createNFTCard(nft, index);
fragment.appendChild(card);
});
elements.nftContainer.innerHTML = '';
elements.nftContainer.appendChild(fragment);
}
// Create NFT Card Element
function createNFTCard(nft, index) {
const card = document.createElement('div');
card.className = 'nft-card';
card.style.animationDelay = `${index * 0.05}s`;
// Check if NFT is for sale
const isForSale = nft.sellOrders && nft.sellOrders.length > 0 && nft.bestOrder;
let price = 'Not Listed';
let priceValue = 0;
if (isForSale && nft.bestOrder.take) {
const decimals = nft.bestOrder.take.type?.decimals || 18;
priceValue = parseFloat(nft.bestOrder.take.value) / Math.pow(10, decimals);
const currency = nft.bestOrder.take.type?.['@type'] === 'ETH' ? 'ETH' :
nft.bestOrder.take.assetType?.assetClass || 'ETH';
price = `${priceValue.toFixed(4)} ${currency}`;
}
// Get image URL
let imageUrl = getImageUrl(nft);
const nftName = nft.meta?.name || `NFT #${nft.tokenId || 'Unknown'}`;
const description = nft.meta?.description || 'No description available';
card.innerHTML = `
<div class="nft-image-container">
<img src="${imageUrl}"
alt="${escapeHtml(nftName)}"
class="nft-image"
loading="lazy"
onerror="this.src='https://via.placeholder.com/400x400/14141f/a0a0b8?text=NFT'">
</div>
<div class="nft-info">
<h3 class="nft-name" title="${escapeHtml(nftName)}">${escapeHtml(nftName)}</h3>
<p class="nft-description">${escapeHtml(description)}</p>
<div class="nft-price ${isForSale ? 'for-sale' : 'not-for-sale'}">${price}</div>
<button class="buy-button"
data-nft-id="${nft.id}"
data-order-id="${isForSale ? nft.bestOrder.id : ''}"
${!isForSale ? 'disabled' : ''}>
${!isForSale ? 'Not Listed' : `Buy for ${price}`}
</button>
</div>
`;
// Add click event to buy button
const buyButton = card.querySelector('.buy-button');
if (buyButton && isForSale) {
buyButton.addEventListener('click', () => handleBuy(nft.id, nft.bestOrder, priceValue));
}
return card;
}
// Get image URL from NFT metadata
function getImageUrl(nft) {
let imageUrl = 'https://via.placeholder.com/400x400/14141f/a0a0b8?text=Loading...';
if (nft.meta?.image) {
if (typeof nft.meta.image === 'string') {
imageUrl = nft.meta.image;
} else if (nft.meta.image?.url) {
imageUrl = nft.meta.image.url.ORIGINAL ||
nft.meta.image.url.BIG ||
nft.meta.image.url.PREVIEW ||
nft.meta.image.url;
} else if (nft.meta.image?.PREVIEW) {
imageUrl = nft.meta.image.PREVIEW;
}
}
// Fallback to content array
if (imageUrl.includes('placeholder') && nft.meta?.content && Array.isArray(nft.meta.content)) {
const imageContent = nft.meta.content.find(item =>
item['@type'] === 'IMAGE' || item.type === 'image'
);
if (imageContent?.url) {
imageUrl = imageContent.url;
}
}
// Convert IPFS URLs
if (imageUrl.startsWith('ipfs://')) {
imageUrl = imageUrl.replace('ipfs://', 'https://ipfs.io/ipfs/');
}
return imageUrl;
}
// Escape HTML to prevent XSS
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Handle Buy Function
async function handleBuy(nftId, order, price) {
if (!window.ethereum || !state.walletAddress) {
showStatus('Please connect wallet first', 'error');
return;
}
try {
const buyButton = document.querySelector(`[data-nft-id="${nftId}"]`);
if (!buyButton) return;
const originalText = buyButton.textContent;
buyButton.disabled = true;
buyButton.textContent = 'Processing...';
showStatus('Starting purchase process...', 'info');
// In production, you would integrate with Rarible SDK or Web3
// For demo purposes:
const confirmBuy = confirm(
`DEMO MODE\n\n` +
`This would purchase the NFT for ${price} ETH.\n\n` +
`In production, this would:\n` +
`1. Verify the order is still active\n` +
`2. Request approval for payment token\n` +
`3. Execute the purchase transaction\n` +
`4. Transfer the NFT to your wallet\n\n` +
`Continue with demo?`
);
if (confirmBuy) {
showStatus('Demo purchase initiated! In production, this would trigger a blockchain transaction.', 'success');
setTimeout(() => {
buyButton.textContent = '✓ Demo Complete';
buyButton.style.background = 'linear-gradient(135deg, #00ff88 0%, #00cc66 100%)';
}, 1500);
} else {
buyButton.disabled = false;
buyButton.textContent = originalText;
}
} catch (error) {
console.error('Error in purchase process:', error);
showStatus('Error in purchase process', 'error');
const buyButton = document.querySelector(`[data-nft-id="${nftId}"]`);
if (buyButton) {
buyButton.disabled = false;
buyButton.textContent = `Buy for ${price} ETH`;
}
}
}
// Apply filter
function applyFilter(filter) {
state.currentFilter = filter;
switch(filter) {
case 'for-sale':
state.filteredNFTs = state.nfts.filter(nft =>
nft.sellOrders && nft.sellOrders.length > 0
);
break;
case 'not-for-sale':
state.filteredNFTs = state.nfts.filter(nft =>
!nft.sellOrders || nft.sellOrders.length === 0
);
break;
default:
state.filteredNFTs = [...state.nfts];
}
renderNFTs();
showStatus(`Showing ${state.filteredNFTs.length} NFTs`, 'info');
}
// Search NFTs
function searchNFTs(searchTerm) {
const term = searchTerm.toLowerCase().trim();
if (term === '') {
state.filteredNFTs = [...state.nfts];
} else {
state.filteredNFTs = state.nfts.filter(nft => {
const name = (nft.meta?.name || '').toLowerCase();
const description = (nft.meta?.description || '').toLowerCase();
const tokenId = (nft.tokenId || '').toString().toLowerCase();
return name.includes(term) ||
description.includes(term) ||
tokenId.includes(term);
});
}
applyFilter(state.currentFilter);
}
// Render states
function renderLoadingState() {
elements.nftContainer.innerHTML = `
<div class="loading">
<div class="spinner"></div>
<p>Loading your NFTs from the blockchain...</p>
</div>
`;
}
function renderEmptyState(message) {
elements.nftContainer.innerHTML = `
<div class="empty-state">
<h3>No NFTs Found</h3>
<p>${message}</p>
</div>
`;
}
function renderErrorState(errorMessage) {
elements.nftContainer.innerHTML = `
<div class="error-state">
<h3>Error Loading NFTs</h3>
<p>${errorMessage}</p>
<p>Please check your connection and try again.</p>
</div>
`;
}
// Setup Event Listeners
function setupEventListeners() {
// Refresh button
elements.refreshBtn.addEventListener('click', fetchNFTs);
// Search with debounce
let searchTimeout;
elements.searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
searchNFTs(e.target.value);
}, 300);
});
// Filter buttons
elements.filterButtons.forEach(button => {
button.addEventListener('click', () => {
elements.filterButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
const filter = button.dataset.filter;
applyFilter(filter);
});
});
}
// Debug utilities
window.debug = {
getState: () => state,
getNFTs: () => state.nfts,
getFilteredNFTs: () => state.filteredNFTs,
reloadNFTs: fetchNFTs,
getSellOrders: (index) => state.nfts[index]?.sellOrders || []
};