🎨 Redesigned Collection Detail Page
Completely rewrote collection detail page to match new design: ✅ Header Section: - Back to collections navigation - TCG filter tabs (MTG, Lorcana, Pokemon) - Upload Image and Generate AI Image buttons - Clean collection title, description, and creator info - Cards count, cost, and timestamp display 🎯 Action Bar: - Share, Favorite, Download List buttons - Invite Collaborator button with proper styling - Public/Private toggle switch - Activity counter with badge 🔍 Enhanced Filters & Search: - Comprehensive filter system (rarities, types, grouping, sorting) - Real-time card search with dropdown results - Grid/List view toggle - Add Cards and Scan Cards buttons 🃏 Game-Grouped Card Display: - Cards organized by game (MTG, Lorcana, Pokemon) - 7-column grid layout matching design - Game section headers with card counts - Empty card placeholders to fill rows - Proper aspect ratios for card display 🎮 Interactive Features: - Working search with live results - Add cards functionality - Public/private toggle - Favorite functionality - Responsive design 🎨 Visual Improvements: - Clean, modern layout matching provided design - Proper spacing and typography - Consistent color scheme - Loading and empty states - Professional action buttons Ready for enhanced collection management! 🚀
This commit is contained in:
parent
4255464dca
commit
891b1c9456
1 changed files with 336 additions and 477 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import Link from 'next/link';
|
||||||
import CollaborationManager from '../../components/CollaborationManager';
|
import CollaborationManager from '../../components/CollaborationManager';
|
||||||
import Layout from '../../components/Layout';
|
import Layout from '../../components/Layout';
|
||||||
|
|
||||||
|
|
@ -19,19 +20,19 @@ export default function CollectionView() {
|
||||||
const [isFavorited, setIsFavorited] = useState(false);
|
const [isFavorited, setIsFavorited] = useState(false);
|
||||||
const [showShareModal, setShowShareModal] = useState(false);
|
const [showShareModal, setShowShareModal] = useState(false);
|
||||||
const [copySuccess, setCopySuccess] = useState(false);
|
const [copySuccess, setCopySuccess] = useState(false);
|
||||||
|
const [selectedTCG, setSelectedTCG] = useState('MTG');
|
||||||
|
|
||||||
// Filter states
|
// Filter states
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [selectedRarity, setSelectedRarity] = useState('all');
|
const [selectedRarity, setSelectedRarity] = useState('All Rarities');
|
||||||
const [selectedType, setSelectedType] = useState('all');
|
const [selectedType, setSelectedType] = useState('All Types');
|
||||||
const [sortBy, setSortBy] = useState('name');
|
const [groupBy, setGroupBy] = useState('Group by Game');
|
||||||
const [viewMode, setViewMode] = useState('grid'); // grid or list
|
const [sortBy, setSortBy] = useState('Sort by Name');
|
||||||
|
const [viewMode, setViewMode] = useState('grid');
|
||||||
const [searchCards, setSearchCards] = useState('');
|
const [searchCards, setSearchCards] = useState('');
|
||||||
const [searchResults, setSearchResults] = useState([]);
|
const [searchResults, setSearchResults] = useState([]);
|
||||||
const [showSearchResults, setShowSearchResults] = useState(false);
|
const [showSearchResults, setShowSearchResults] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
fetchCollectionData();
|
fetchCollectionData();
|
||||||
|
|
@ -59,28 +60,8 @@ export default function CollectionView() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyLink = async () => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(window.location.href);
|
|
||||||
setCopySuccess(true);
|
|
||||||
setTimeout(() => setCopySuccess(false), 2000);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to copy link:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFavorite = () => {
|
|
||||||
setIsFavorited(!isFavorited);
|
|
||||||
// Here you would typically make an API call
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveCopy = () => {
|
|
||||||
// Logic to save a copy to user's collections
|
|
||||||
console.log('Saving copy of collection');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearchCards = async (query) => {
|
const handleSearchCards = async (query) => {
|
||||||
if (!query.trim()) {
|
if (query.length < 2) {
|
||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
setShowSearchResults(false);
|
setShowSearchResults(false);
|
||||||
return;
|
return;
|
||||||
|
|
@ -89,8 +70,8 @@ export default function CollectionView() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/cards/search?q=${encodeURIComponent(query)}&limit=10`);
|
const response = await fetch(`/api/cards/search?q=${encodeURIComponent(query)}&limit=10`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const results = await response.json();
|
const data = await response.json();
|
||||||
setSearchResults(results);
|
setSearchResults(data.cards || []);
|
||||||
setShowSearchResults(true);
|
setShowSearchResults(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -98,65 +79,86 @@ export default function CollectionView() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddCard = async (card, quantity = 1) => {
|
const handleAddCard = async (card) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/collections/${id}/cards`, {
|
const response = await fetch(`/api/collections/${id}/cards`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
cardId: card.id,
|
cardId: card.id,
|
||||||
quantity
|
quantity: 1
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// Refresh collection data
|
|
||||||
fetchCollectionData();
|
|
||||||
setSearchCards('');
|
setSearchCards('');
|
||||||
setSearchResults([]);
|
|
||||||
setShowSearchResults(false);
|
setShowSearchResults(false);
|
||||||
} else {
|
fetchCollectionData(); // Refresh the collection data
|
||||||
const error = await response.json();
|
|
||||||
alert(error.error || 'Failed to add card');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding card:', error);
|
console.error('Error adding card:', error);
|
||||||
alert('Network error. Please try again.');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredCards = cards.filter(card => {
|
const handleShare = () => {
|
||||||
const matchesSearch = card.name.toLowerCase().includes(searchQuery.toLowerCase());
|
const url = window.location.href;
|
||||||
const matchesRarity = selectedRarity === 'all' || card.rarity === selectedRarity;
|
navigator.clipboard.writeText(url).then(() => {
|
||||||
const matchesType = selectedType === 'all' || card.card_type === selectedType;
|
setCopySuccess(true);
|
||||||
return matchesSearch && matchesRarity && matchesType;
|
setTimeout(() => setCopySuccess(false), 2000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleFavorite = () => {
|
||||||
|
setIsFavorited(!isFavorited);
|
||||||
|
};
|
||||||
|
|
||||||
|
const togglePublic = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/collections/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
is_public: !collection.is_public
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedCards = [...filteredCards].sort((a, b) => {
|
if (response.ok) {
|
||||||
switch (sortBy) {
|
setCollection(prev => ({
|
||||||
case 'name':
|
...prev,
|
||||||
return a.name.localeCompare(b.name);
|
is_public: !prev.is_public
|
||||||
case 'cost':
|
}));
|
||||||
return b.market_price - a.market_price;
|
|
||||||
case 'rarity':
|
|
||||||
return a.rarity.localeCompare(b.rarity);
|
|
||||||
case 'type':
|
|
||||||
return a.card_type.localeCompare(b.card_type);
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error('Error updating collection:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const totalValue = cards.reduce((sum, card) => sum + (card.market_price * card.quantity), 0);
|
// Group cards by game
|
||||||
const totalCards = cards.reduce((sum, card) => sum + card.quantity, 0);
|
const groupedCards = cards.reduce((acc, card) => {
|
||||||
|
const game = card.game || 'Other';
|
||||||
|
if (!acc[game]) acc[game] = [];
|
||||||
|
acc[game].push(card);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Get game display names and counts
|
||||||
|
const gameStats = {
|
||||||
|
'MTG': groupedCards['MTG']?.length || 0,
|
||||||
|
'Lorcana': groupedCards['Lorcana']?.length || 0,
|
||||||
|
'Pokemon': groupedCards['Pokemon']?.length || 0
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Layout user={user}>
|
<Layout user={user}>
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen">
|
||||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2" style={{ borderColor: 'var(--text-accent)' }}></div>
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-purple-600"></div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
@ -170,12 +172,11 @@ export default function CollectionView() {
|
||||||
<h2 className="text-2xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h2 className="text-2xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Collection not found
|
Collection not found
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<Link href="/collections">
|
||||||
onClick={() => router.push('/collections')}
|
<button className="px-4 py-2 rounded-lg gradient-bg-purple text-white">
|
||||||
className="px-6 py-3 rounded-lg font-medium gradient-bg-purple text-white hover:shadow-lg transform hover:scale-105 transition-all duration-200"
|
|
||||||
>
|
|
||||||
Back to Collections
|
Back to Collections
|
||||||
</button>
|
</button>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
@ -184,227 +185,225 @@ export default function CollectionView() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout user={user}>
|
<Layout user={user}>
|
||||||
{/* Hero Section with Collection Info */}
|
<div className="min-h-screen" style={{ backgroundColor: 'var(--bg-primary)' }}>
|
||||||
<div
|
{/* Header */}
|
||||||
className="relative h-64 bg-gradient-to-r from-purple-600 to-blue-600 flex items-end"
|
<div className="p-6 border-b" style={{ backgroundColor: 'var(--bg-secondary)', borderColor: 'var(--border)' }}>
|
||||||
style={{
|
<div className="flex items-center justify-between mb-6">
|
||||||
backgroundImage: collection.image
|
{/* Back button and TCG tabs */}
|
||||||
? `linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.6)), url("${collection.image}")`
|
<div className="flex items-center space-x-6">
|
||||||
: 'linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.6)), url("https://images.unsplash.com/photo-1606092195730-5d7b9af1efc5?w=1200")',
|
<Link href="/collections">
|
||||||
backgroundSize: 'cover',
|
<button className="flex items-center text-sm font-medium hover:underline" style={{ color: 'var(--text-secondary)' }}>
|
||||||
backgroundPosition: 'center'
|
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
}}
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
Back to collections
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* TCG Tabs */}
|
||||||
|
<div className="flex space-x-1">
|
||||||
|
{['MTG', 'Lorcana', 'Pokemon'].map((tcg) => (
|
||||||
|
<button
|
||||||
|
key={tcg}
|
||||||
|
onClick={() => setSelectedTCG(tcg)}
|
||||||
|
className={`px-3 py-1 text-xs font-medium rounded ${
|
||||||
|
selectedTCG === tcg
|
||||||
|
? 'bg-blue-100 text-blue-800'
|
||||||
|
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div className="container mx-auto px-6 pb-8">
|
{tcg}
|
||||||
<div className="flex items-center space-x-4 mb-4">
|
</button>
|
||||||
<div className="p-3 rounded-xl bg-white bg-opacity-20 backdrop-blur-sm">
|
))}
|
||||||
<div className="text-2xl">🃏</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<h1 className="text-4xl font-bold text-white mb-2">
|
|
||||||
|
{/* Action buttons */}
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<button className="px-4 py-2 text-sm font-medium border rounded-lg hover:bg-gray-50" style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}>
|
||||||
|
Upload Image
|
||||||
|
</button>
|
||||||
|
<button className="px-4 py-2 text-sm font-medium bg-purple-600 text-white rounded-lg hover:bg-purple-700">
|
||||||
|
🪄 Generate AI Image
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Collection Info */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h1 className="text-3xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||||
{collection.name}
|
{collection.name}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex items-center space-x-4 text-white text-opacity-90">
|
<p className="text-lg mb-4" style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
{collection.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Creator and Stats */}
|
||||||
|
<div className="flex items-center space-x-6 text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="w-6 h-6 rounded-full bg-white bg-opacity-20 flex items-center justify-center">
|
<span>Crafted by</span>
|
||||||
<span className="text-sm">👤</span>
|
<div className="flex items-center space-x-1">
|
||||||
</div>
|
<div className="w-6 h-6 bg-purple-600 rounded-full flex items-center justify-center">
|
||||||
<span>Created by {collection.creator_email}</span>
|
<span className="text-white text-xs font-bold">
|
||||||
</div>
|
{collection.creator_email?.charAt(0).toUpperCase()}
|
||||||
<span>•</span>
|
|
||||||
<span>TCG: {collection.tcg}</span>
|
|
||||||
<span>•</span>
|
|
||||||
<span>Value: ${totalValue.toFixed(2)}</span>
|
|
||||||
<span>•</span>
|
|
||||||
<span>Cards: {totalCards}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2 text-white text-opacity-75 text-sm">
|
|
||||||
<span>Created {new Date(collection.created_at).toLocaleDateString()}</span>
|
|
||||||
<span>•</span>
|
|
||||||
<span>Last updated {new Date(collection.updated_at).toLocaleDateString()}</span>
|
|
||||||
{collection.is_public && (
|
|
||||||
<>
|
|
||||||
<span>•</span>
|
|
||||||
<span className="px-2 py-1 rounded-full bg-white bg-opacity-20 text-xs">
|
|
||||||
🌍 Public
|
|
||||||
</span>
|
</span>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
<span className="font-medium">{collection.creator_email}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>Cards: <span className="font-medium">{cards.length}</span></div>
|
||||||
|
<div>Cost: <span className="font-medium">${collection.totalValue || '0'}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-4 mt-2 text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
<span>Created {new Date(collection.created_at).toLocaleDateString()}</span>
|
||||||
|
<span>Last updated {new Date(collection.updated_at).toLocaleDateString()}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Bar */}
|
||||||
<div className="border-b" style={{ backgroundColor: 'var(--bg-secondary)', borderColor: 'var(--border)' }}>
|
|
||||||
<div className="container mx-auto px-6 py-4">
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-4">
|
||||||
<button
|
<button
|
||||||
onClick={handleCopyLink}
|
onClick={handleShare}
|
||||||
className="flex items-center space-x-2 px-4 py-2 rounded-lg border transition-all duration-200 hover:shadow-md"
|
className="flex items-center space-x-2 px-3 py-2 text-sm font-medium border rounded-lg hover:bg-gray-50"
|
||||||
style={{
|
style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
||||||
</svg>
|
|
||||||
<span>{copySuccess ? 'Copied!' : 'Copy link'}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => setShowShareModal(true)}
|
|
||||||
className="flex items-center space-x-2 px-4 py-2 rounded-lg border transition-all duration-200 hover:shadow-md"
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Copy list</span>
|
<span>Share</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="flex items-center space-x-2 px-4 py-2 rounded-lg border transition-all duration-200 hover:shadow-md"
|
onClick={toggleFavorite}
|
||||||
style={{
|
className={`flex items-center space-x-2 px-3 py-2 text-sm font-medium border rounded-lg ${
|
||||||
backgroundColor: 'var(--bg-primary)',
|
isFavorited ? 'bg-red-50 border-red-200 text-red-600' : 'hover:bg-gray-50'
|
||||||
borderColor: 'var(--border)',
|
}`}
|
||||||
color: 'var(--text-primary)'
|
style={!isFavorited ? { borderColor: 'var(--border)', color: 'var(--text-primary)' } : {}}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill={isFavorited ? 'currentColor' : 'none'} stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H9.414a1 1 0 01-.707-.293l-2-2A1 1 0 006 6H4a2 2 0 00-2 2v11a2 2 0 002 2h4a2 2 0 002-2v-1" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Print proxies</span>
|
<span>Favorite</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="flex items-center space-x-2 px-3 py-2 text-sm font-medium border rounded-lg hover:bg-gray-50" style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
<span>Download List</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<button className="px-4 py-2 text-sm font-medium bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
||||||
|
📧 Invite Collaborator
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>Public</span>
|
||||||
<button
|
<button
|
||||||
onClick={handleSaveCopy}
|
onClick={togglePublic}
|
||||||
className="flex items-center space-x-2 px-6 py-2 rounded-lg font-medium gradient-bg-purple text-white hover:shadow-lg transform hover:scale-105 transition-all duration-200"
|
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||||||
|
collection.is_public ? 'bg-blue-600' : 'bg-gray-200'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<span
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3-3m0 0l-3 3m3-3v12" />
|
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||||
</svg>
|
collection.is_public ? 'translate-x-6' : 'translate-x-1'
|
||||||
<span>Save deck</span>
|
}`}
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats and Filters */}
|
|
||||||
<div className="container mx-auto px-6 py-6">
|
|
||||||
{/* Collection Stats */}
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
|
||||||
<div className="text-center p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
|
||||||
<div className="text-2xl font-bold gradient-text-blue">{totalCards}</div>
|
|
||||||
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>Total Cards</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-center p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
|
||||||
<div className="text-2xl font-bold gradient-text-purple">${totalValue.toFixed(2)}</div>
|
|
||||||
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>Total Value</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-center p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
|
||||||
<div className="text-2xl font-bold gradient-text-green">{collection.tcg}</div>
|
|
||||||
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>Game</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-center p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
|
||||||
<div className="text-2xl font-bold gradient-text-orange">{collection.is_public ? 'Public' : 'Private'}</div>
|
|
||||||
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>Visibility</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Collaboration Manager */}
|
|
||||||
<CollaborationManager
|
|
||||||
collectionId={id}
|
|
||||||
isOwner={collection.creator_email === user.email}
|
|
||||||
/>
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Filters */}
|
<div className="flex items-center space-x-1 text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||||||
<div className="p-6 rounded-xl mb-6" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<span>Activity</span>
|
||||||
<div className="flex flex-col lg:flex-row gap-4">
|
<span className="px-2 py-1 bg-purple-100 text-purple-800 rounded-full text-xs font-medium">123</span>
|
||||||
<div className="flex-1">
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="p-6">
|
||||||
|
{/* Search and Filters */}
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search cards..."
|
placeholder="Search cards..."
|
||||||
className="w-full px-4 py-3 rounded-lg border transition-all duration-200 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
value={searchCards}
|
||||||
style={{
|
onChange={(e) => {
|
||||||
backgroundColor: 'var(--bg-primary)',
|
setSearchCards(e.target.value);
|
||||||
borderColor: 'var(--border)',
|
handleSearchCards(e.target.value);
|
||||||
color: 'var(--text-primary)'
|
|
||||||
}}
|
}}
|
||||||
value={searchQuery}
|
className="w-64 px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-secondary)' }}
|
||||||
/>
|
/>
|
||||||
|
<svg className="absolute right-3 top-2.5 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-3">
|
|
||||||
<select
|
<select
|
||||||
value={selectedRarity}
|
value={selectedRarity}
|
||||||
onChange={(e) => setSelectedRarity(e.target.value)}
|
onChange={(e) => setSelectedRarity(e.target.value)}
|
||||||
className="px-4 py-3 rounded-lg border transition-all duration-200"
|
className="px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||||
style={{
|
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-secondary)' }}
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<option value="all">All Rarities</option>
|
<option>All Rarities</option>
|
||||||
<option value="Common">Common</option>
|
<option>Common</option>
|
||||||
<option value="Uncommon">Uncommon</option>
|
<option>Uncommon</option>
|
||||||
<option value="Rare">Rare</option>
|
<option>Rare</option>
|
||||||
<option value="Ultra Rare">Ultra Rare</option>
|
<option>Mythic</option>
|
||||||
<option value="Rainbow Rare">Rainbow Rare</option>
|
<option>Legendary</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={selectedType}
|
value={selectedType}
|
||||||
onChange={(e) => setSelectedType(e.target.value)}
|
onChange={(e) => setSelectedType(e.target.value)}
|
||||||
className="px-4 py-3 rounded-lg border transition-all duration-200"
|
className="px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||||
style={{
|
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-secondary)' }}
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<option value="all">All Types</option>
|
<option>All Types</option>
|
||||||
<option value="Electric">Electric</option>
|
<option>Creature</option>
|
||||||
<option value="Trainer">Trainer</option>
|
<option>Instant</option>
|
||||||
<option value="Energy">Energy</option>
|
<option>Sorcery</option>
|
||||||
|
<option>Artifact</option>
|
||||||
|
<option>Enchantment</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select
|
||||||
|
value={groupBy}
|
||||||
|
onChange={(e) => setGroupBy(e.target.value)}
|
||||||
|
className="px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||||
|
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-secondary)' }}
|
||||||
|
>
|
||||||
|
<option>Group by Game</option>
|
||||||
|
<option>Group by Rarity</option>
|
||||||
|
<option>Group by Type</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={sortBy}
|
value={sortBy}
|
||||||
onChange={(e) => setSortBy(e.target.value)}
|
onChange={(e) => setSortBy(e.target.value)}
|
||||||
className="px-4 py-3 rounded-lg border transition-all duration-200"
|
className="px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||||
style={{
|
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-secondary)' }}
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<option value="name">Sort by Name</option>
|
<option>Sort by Name</option>
|
||||||
<option value="cost">Sort by Price</option>
|
<option>Sort by Price</option>
|
||||||
<option value="rarity">Sort by Rarity</option>
|
<option>Sort by Rarity</option>
|
||||||
<option value="type">Sort by Type</option>
|
<option>Sort by Date Added</option>
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
<div className="flex rounded-lg border" style={{ borderColor: 'var(--border)' }}>
|
<div className="flex rounded-lg border" style={{ borderColor: 'var(--border)' }}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setViewMode('grid')}
|
onClick={() => setViewMode('grid')}
|
||||||
className={`p-3 rounded-l-lg transition-all duration-200 ${
|
className={`p-2 rounded-l-lg ${viewMode === 'grid' ? 'bg-purple-600 text-white' : 'bg-transparent'}`}
|
||||||
viewMode === 'grid'
|
|
||||||
? 'gradient-bg-purple text-white'
|
|
||||||
: 'bg-transparent'
|
|
||||||
}`}
|
|
||||||
style={viewMode !== 'grid' ? { color: 'var(--text-secondary)' } : {}}
|
style={viewMode !== 'grid' ? { color: 'var(--text-secondary)' } : {}}
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -413,11 +412,7 @@ export default function CollectionView() {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setViewMode('list')}
|
onClick={() => setViewMode('list')}
|
||||||
className={`p-3 rounded-r-lg transition-all duration-200 ${
|
className={`p-2 rounded-r-lg ${viewMode === 'list' ? 'bg-purple-600 text-white' : 'bg-transparent'}`}
|
||||||
viewMode === 'list'
|
|
||||||
? 'gradient-bg-purple text-white'
|
|
||||||
: 'bg-transparent'
|
|
||||||
}`}
|
|
||||||
style={viewMode !== 'list' ? { color: 'var(--text-secondary)' } : {}}
|
style={viewMode !== 'list' ? { color: 'var(--text-secondary)' } : {}}
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -425,248 +420,112 @@ export default function CollectionView() {
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Cards Display */}
|
<button className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 flex items-center space-x-2">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<h2 className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
|
|
||||||
Cards ({sortedCards.length})
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{viewMode === 'grid' ? (
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-4">
|
|
||||||
{sortedCards.map(card => (
|
|
||||||
<div
|
|
||||||
key={card.id}
|
|
||||||
className="group cursor-pointer transition-all duration-300 hover:scale-105"
|
|
||||||
onClick={() => router.push(`/card/${card.id}`)}
|
|
||||||
>
|
|
||||||
<div className="relative rounded-xl overflow-hidden shadow-lg group-hover:shadow-xl transition-all duration-300">
|
|
||||||
<img
|
|
||||||
src={card.image_url}
|
|
||||||
alt={card.name}
|
|
||||||
className="w-full h-auto object-cover"
|
|
||||||
onError={(e) => {
|
|
||||||
e.target.src = 'https://via.placeholder.com/250x350/6366f1/ffffff?text=No+Image';
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{card.quantity > 1 && (
|
|
||||||
<div className="absolute top-2 right-2 bg-black bg-opacity-75 text-white text-xs px-2 py-1 rounded-full">
|
|
||||||
{card.quantity}x
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent p-3">
|
|
||||||
<div className="text-white text-sm font-medium truncate">
|
|
||||||
{card.name}
|
|
||||||
</div>
|
|
||||||
<div className="text-white text-opacity-75 text-xs">
|
|
||||||
${card.market_price}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{sortedCards.map(card => (
|
|
||||||
<div
|
|
||||||
key={card.id}
|
|
||||||
className="flex items-center p-4 rounded-xl border transition-all duration-200 hover:shadow-md cursor-pointer"
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-secondary)',
|
|
||||||
borderColor: 'var(--border)'
|
|
||||||
}}
|
|
||||||
onClick={() => router.push(`/card/${card.id}`)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={card.image_url}
|
|
||||||
alt={card.name}
|
|
||||||
className="w-16 h-22 object-cover rounded-lg mr-4"
|
|
||||||
onError={(e) => {
|
|
||||||
e.target.src = 'https://via.placeholder.com/64x88/6366f1/ffffff?text=No+Image';
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="flex-1">
|
|
||||||
<h3 className="font-semibold" style={{ color: 'var(--text-primary)' }}>
|
|
||||||
{card.name}
|
|
||||||
</h3>
|
|
||||||
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
|
||||||
{card.set_name} • {card.rarity} • {card.card_type}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<div className="font-semibold" style={{ color: 'var(--text-primary)' }}>
|
|
||||||
${card.market_price}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
|
||||||
Qty: {card.quantity}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{cards.length === 0 ? (
|
|
||||||
// Empty collection state
|
|
||||||
<div className="text-center py-16">
|
|
||||||
<div className="text-8xl mb-6">🃏</div>
|
|
||||||
<h3 className="text-2xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
|
|
||||||
Start Building Your Collection
|
|
||||||
</h3>
|
|
||||||
<p className="text-lg mb-8 max-w-md mx-auto" style={{ color: 'var(--text-secondary)' }}>
|
|
||||||
This collection is empty. Add your first cards to get started!
|
|
||||||
</p>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<button
|
|
||||||
onClick={() => router.push('/cards')}
|
|
||||||
className="inline-flex items-center space-x-2 px-8 py-4 rounded-xl font-medium gradient-bg-purple text-white hover:shadow-lg transform hover:scale-105 transition-all duration-200"
|
|
||||||
>
|
|
||||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Browse Cards to Add</span>
|
<span>Add Cards</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="px-4 py-2 border rounded-lg hover:bg-gray-50 flex items-center space-x-2" style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>Scan Cards</span>
|
||||||
</button>
|
</button>
|
||||||
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
|
||||||
Or search for specific cards to add to your collection
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick add section */}
|
{/* Game Sections */}
|
||||||
<div className="mt-12 p-6 rounded-2xl max-w-md mx-auto relative" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
{Object.entries(groupedCards).map(([game, gameCards]) => (
|
||||||
<h4 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<div key={game} className="mb-8">
|
||||||
Quick Add
|
<div className="flex items-center justify-between mb-4">
|
||||||
</h4>
|
<h2 className="text-xl font-bold flex items-center space-x-3" style={{ color: 'var(--text-primary)' }}>
|
||||||
<div className="space-y-3">
|
<span>{game === 'MTG' ? 'Magic The Gathering' : game}</span>
|
||||||
<input
|
<span className="px-2 py-1 bg-gray-100 text-gray-600 rounded text-sm font-medium">
|
||||||
type="text"
|
{gameCards.length}
|
||||||
placeholder="Search for a card to add..."
|
</span>
|
||||||
value={searchCards}
|
</h2>
|
||||||
onChange={(e) => {
|
</div>
|
||||||
setSearchCards(e.target.value);
|
|
||||||
handleSearchCards(e.target.value);
|
<div className="grid grid-cols-7 gap-4">
|
||||||
}}
|
{gameCards.map((card, index) => (
|
||||||
className="w-full px-4 py-3 rounded-lg border transition-all duration-200"
|
<div key={card.id || index} className="aspect-[2.5/3.5] bg-gray-200 rounded-lg flex items-center justify-center text-gray-500 text-sm font-medium hover:bg-gray-300 transition-colors cursor-pointer">
|
||||||
style={{
|
{card.image_url ? (
|
||||||
backgroundColor: 'var(--bg-primary)',
|
<img
|
||||||
borderColor: 'var(--border)',
|
src={card.image_url}
|
||||||
color: 'var(--text-primary)'
|
alt={card.name}
|
||||||
}}
|
className="w-full h-full object-cover rounded-lg"
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
'Card'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
{/* Search Results */}
|
{/* Add empty card slots to fill the row */}
|
||||||
|
{Array.from({ length: Math.max(0, 7 - (gameCards.length % 7)) }, (_, index) => (
|
||||||
|
<div key={`empty-${index}`} className="aspect-[2.5/3.5] bg-gray-200 rounded-lg flex items-center justify-center text-gray-500 text-sm font-medium hover:bg-gray-300 transition-colors cursor-pointer">
|
||||||
|
Card
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{cards.length === 0 && (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<div className="text-6xl mb-4">🃏</div>
|
||||||
|
<h3 className="text-xl font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
Start Building Your Collection
|
||||||
|
</h3>
|
||||||
|
<p className="mb-6" style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
Add cards to get started with your collection
|
||||||
|
</p>
|
||||||
|
<button className="px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700">
|
||||||
|
Browse Cards to Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search Results Dropdown */}
|
||||||
{showSearchResults && searchResults.length > 0 && (
|
{showSearchResults && searchResults.length > 0 && (
|
||||||
<div className="absolute top-full left-0 right-0 mt-1 bg-white border rounded-lg shadow-lg z-10 max-h-60 overflow-y-auto">
|
<div className="absolute top-full left-0 right-0 bg-white border border-gray-200 rounded-lg shadow-lg max-h-64 overflow-y-auto z-50">
|
||||||
{searchResults.map((card) => (
|
{searchResults.map(card => (
|
||||||
<div
|
<div
|
||||||
key={card.id}
|
key={card.id}
|
||||||
className="flex items-center p-3 hover:bg-gray-50 cursor-pointer border-b last:border-b-0"
|
|
||||||
onClick={() => handleAddCard(card)}
|
onClick={() => handleAddCard(card)}
|
||||||
|
className="flex items-center p-3 hover:bg-gray-50 cursor-pointer"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={card.image_url}
|
src={card.image_url}
|
||||||
alt={card.name}
|
alt={card.name}
|
||||||
className="w-10 h-14 object-cover rounded mr-3"
|
className="w-12 h-16 object-cover rounded mr-3"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
e.target.src = 'https://via.placeholder.com/40x56/6366f1/ffffff?text=?';
|
e.target.src = 'https://via.placeholder.com/48x64/6366f1/ffffff?text=No+Image';
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="flex-1">
|
<div>
|
||||||
<div className="font-medium text-gray-900">{card.name}</div>
|
<div className="font-medium">{card.name}</div>
|
||||||
<div className="text-sm text-gray-500">{card.set_name} • {card.rarity}</div>
|
<div className="text-sm text-gray-500">{card.set_name} • ${card.market_price}</div>
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<div className="font-medium text-gray-900">${card.market_price}</div>
|
|
||||||
<div className="text-xs text-gray-500">{card.game}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : sortedCards.length === 0 ? (
|
|
||||||
// No results for current filters
|
|
||||||
<div className="text-center py-12">
|
|
||||||
<div className="text-6xl mb-4">🔍</div>
|
|
||||||
<h3 className="text-xl font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
|
|
||||||
No cards match your filters
|
|
||||||
</h3>
|
|
||||||
<p style={{ color: 'var(--text-secondary)' }}>
|
|
||||||
Try adjusting your search or filter criteria
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setSearchQuery('');
|
|
||||||
setSelectedRarity('all');
|
|
||||||
setSelectedType('all');
|
|
||||||
}}
|
|
||||||
className="mt-4 px-4 py-2 rounded-lg border transition-all duration-200"
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear All Filters
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Share Modal */}
|
{/* Collaboration Manager */}
|
||||||
{showShareModal && (
|
<div className="p-6 border-t" style={{ borderColor: 'var(--border)' }}>
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<CollaborationManager
|
||||||
<div className="p-6 rounded-2xl shadow-lg max-w-md w-full mx-4" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
collectionId={id}
|
||||||
<h2 className="text-2xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
|
userRole={collection.userRole}
|
||||||
Share Collection
|
isPublic={collection.is_public}
|
||||||
</h2>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
|
||||||
Collection URL
|
|
||||||
</label>
|
|
||||||
<div className="flex">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={window.location.href}
|
|
||||||
readOnly
|
|
||||||
className="flex-1 px-4 py-2 rounded-l-lg border"
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)'
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
onClick={handleCopyLink}
|
|
||||||
className="px-4 py-2 rounded-r-lg gradient-bg-purple text-white hover:shadow-lg transition-all duration-200"
|
|
||||||
>
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="flex space-x-3 mt-6">
|
|
||||||
<button
|
|
||||||
onClick={() => setShowShareModal(false)}
|
|
||||||
className="flex-1 py-2 px-4 rounded-xl border transition-colors"
|
|
||||||
style={{
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-secondary)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue