Added comprehensive debug logging to understand why card images aren't displaying: - Log collection name, thumbnails data, and custom image - Log mainCard and gridCards data - Add onError and onLoad handlers for images - Log when showing crying emoji placeholder This will help identify if the issue is with data flow or image loading.
916 lines
No EOL
36 KiB
JavaScript
916 lines
No EOL
36 KiB
JavaScript
import { useState, useEffect } from 'react';
|
||
import { useRouter } from 'next/router';
|
||
import Layout from '../components/Layout';
|
||
import PermissionIndicator from '../components/PermissionIndicator';
|
||
import { useAuth } from '../lib/use-auth';
|
||
import Link from 'next/link';
|
||
|
||
export default function Collections() {
|
||
const router = useRouter();
|
||
const { user, loading: authLoading } = useAuth();
|
||
|
||
const [collections, setCollections] = useState([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||
const [editingCollection, setEditingCollection] = useState(null);
|
||
const [searchQuery, setSearchQuery] = useState('');
|
||
const [sortBy, setSortBy] = useState('name'); // name, value, cardCount, createdAt
|
||
|
||
const [newCollection, setNewCollection] = useState({
|
||
name: '',
|
||
description: '',
|
||
isPublic: false,
|
||
image: '',
|
||
tags: []
|
||
});
|
||
const [showSuccessModal, setShowSuccessModal] = useState(false);
|
||
const [createdCollection, setCreatedCollection] = useState(null);
|
||
const [tagInput, setTagInput] = useState(''); // For creating new collections
|
||
const [editTagInput, setEditTagInput] = useState(''); // For editing collections
|
||
|
||
// Redirect to login if not authenticated
|
||
useEffect(() => {
|
||
if (!authLoading && !user) {
|
||
router.push('/login');
|
||
}
|
||
}, [authLoading, user, router]);
|
||
|
||
useEffect(() => {
|
||
if (user) {
|
||
fetchCollections();
|
||
}
|
||
}, [user]);
|
||
|
||
const fetchCollections = async () => {
|
||
try {
|
||
const token = localStorage.getItem('auth_token');
|
||
const headers = {
|
||
'Content-Type': 'application/json',
|
||
};
|
||
|
||
if (token) {
|
||
headers.Authorization = `Bearer ${token}`;
|
||
}
|
||
|
||
const response = await fetch('/api/collections', { headers });
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
|
||
// Fetch thumbnails for each collection
|
||
const collectionsWithThumbnails = await Promise.all(
|
||
data.map(async (collection) => {
|
||
try {
|
||
const identifier = collection.slug || collection.id;
|
||
const thumbnailResponse = await fetch(`/api/collections/${identifier}/thumbnails`, { headers });
|
||
if (thumbnailResponse.ok) {
|
||
const thumbnailData = await thumbnailResponse.json();
|
||
return { ...collection, thumbnails: thumbnailData.thumbnails };
|
||
}
|
||
return { ...collection, thumbnails: [] };
|
||
} catch (error) {
|
||
console.error(`Error fetching thumbnails for collection ${collection.id}:`, error);
|
||
return { ...collection, thumbnails: [] };
|
||
}
|
||
})
|
||
);
|
||
|
||
setCollections(collectionsWithThumbnails);
|
||
} else {
|
||
console.error('Failed to fetch collections');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error fetching collections:', error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const sortOptions = [
|
||
{ value: 'name', label: 'Name (A-Z)' },
|
||
{ value: 'value', label: 'Value (High to Low)' },
|
||
{ value: 'cardCount', label: 'Card Count (High to Low)' },
|
||
{ value: 'createdAt', label: 'Date Created (Newest)' }
|
||
];
|
||
|
||
const sortCollections = (collections, sortBy) => {
|
||
return [...collections].sort((a, b) => {
|
||
switch (sortBy) {
|
||
case 'name':
|
||
return a.name.localeCompare(b.name);
|
||
case 'value':
|
||
return b.value - a.value;
|
||
case 'cardCount':
|
||
return b.cardCount - a.cardCount;
|
||
case 'createdAt':
|
||
return new Date(b.createdAt) - new Date(a.createdAt);
|
||
default:
|
||
return 0;
|
||
}
|
||
});
|
||
};
|
||
|
||
const filteredCollections = collections.filter(collection => {
|
||
const matchesSearch = collection.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||
collection.description.toLowerCase().includes(searchQuery.toLowerCase());
|
||
return matchesSearch;
|
||
});
|
||
|
||
const sortedCollections = sortCollections(filteredCollections, sortBy);
|
||
|
||
const handleCreateCollection = async () => {
|
||
try {
|
||
const response = await fetch('/api/collections', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
name: newCollection.name,
|
||
description: newCollection.description,
|
||
isPublic: newCollection.isPublic,
|
||
image: newCollection.image,
|
||
tags: newCollection.tags
|
||
})
|
||
});
|
||
|
||
if (response.ok) {
|
||
const createdCollection = await response.json();
|
||
setCreatedCollection(createdCollection);
|
||
setShowCreateModal(false);
|
||
setShowSuccessModal(true);
|
||
|
||
// Reset form
|
||
setNewCollection({
|
||
name: '',
|
||
description: '',
|
||
isPublic: false,
|
||
image: '',
|
||
tags: []
|
||
});
|
||
setTagInput(''); // Clear tag input
|
||
|
||
// Refresh collections list
|
||
fetchCollections();
|
||
} else {
|
||
const error = await response.json();
|
||
alert(error.error || 'Failed to create collection');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error creating collection:', error);
|
||
alert('Network error. Please try again.');
|
||
}
|
||
};
|
||
|
||
const handleUpdateCollection = async () => {
|
||
try {
|
||
const response = await fetch(`/api/collections/${editingCollection.slug || editingCollection.id}`, {
|
||
method: 'PUT',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
name: editingCollection.name,
|
||
description: editingCollection.description,
|
||
isPublic: editingCollection.isPublic,
|
||
image: editingCollection.image,
|
||
tags: editingCollection.tags
|
||
})
|
||
});
|
||
|
||
if (response.ok) {
|
||
// Refresh collections after update
|
||
fetchCollections();
|
||
setEditingCollection(null);
|
||
setEditTagInput(''); // Clear the tag input
|
||
} else {
|
||
const error = await response.json();
|
||
alert(error.error || 'Failed to update collection');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error updating collection:', error);
|
||
alert('Network error. Please try again.');
|
||
}
|
||
};
|
||
|
||
const handleDeleteCollection = async (collectionId) => {
|
||
if (confirm('Are you sure you want to delete this collection? This action cannot be undone.')) {
|
||
try {
|
||
const response = await fetch(`/api/collections/${collectionId}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
if (response.ok) {
|
||
fetchCollections(); // Refresh the list
|
||
} else {
|
||
const error = await response.json();
|
||
alert(error.error || 'Failed to delete collection');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error deleting collection:', error);
|
||
alert('Network error. Please try again.');
|
||
}
|
||
}
|
||
};
|
||
|
||
// Tag handling functions
|
||
const handleAddTag = (tagInput, setTagInput, collection, setCollection) => {
|
||
if (tagInput.trim() && !collection.tags.includes(tagInput.trim())) {
|
||
setCollection({
|
||
...collection,
|
||
tags: [...collection.tags, tagInput.trim()]
|
||
});
|
||
setTagInput('');
|
||
}
|
||
};
|
||
|
||
const handleRemoveTag = (tagToRemove, collection, setCollection) => {
|
||
setCollection({
|
||
...collection,
|
||
tags: collection.tags.filter(tag => tag !== tagToRemove)
|
||
});
|
||
};
|
||
|
||
const handleTagKeyPress = (e, tagInput, setTagInput, collection, setCollection) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
handleAddTag(tagInput, setTagInput, collection, setCollection);
|
||
}
|
||
};
|
||
|
||
const formatCurrency = (amount) => {
|
||
return new Intl.NumberFormat('en-US', {
|
||
style: 'currency',
|
||
currency: 'USD'
|
||
}).format(amount);
|
||
};
|
||
|
||
const formatDate = (dateString) => {
|
||
return new Date(dateString).toLocaleDateString();
|
||
};
|
||
|
||
// Collection thumbnail component
|
||
const CollectionThumbnail = ({ collection }) => {
|
||
const { thumbnails = [], image } = collection;
|
||
|
||
console.log('🔍 CollectionThumbnail - collection:', collection.name);
|
||
console.log('🔍 CollectionThumbnail - thumbnails:', thumbnails);
|
||
console.log('🔍 CollectionThumbnail - image:', image);
|
||
|
||
// If there's a custom image, show it
|
||
if (image) {
|
||
return (
|
||
<div className="w-full h-48 rounded-xl overflow-hidden mb-4">
|
||
<img
|
||
src={image}
|
||
alt={collection.name}
|
||
className="w-full h-full object-cover"
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// If no cards, show crying emoji
|
||
if (!thumbnails || thumbnails.length === 0) {
|
||
console.log('🔍 CollectionThumbnail - showing crying emoji (no thumbnails)');
|
||
return (
|
||
<div className="w-full h-48 rounded-xl mb-4 flex items-center justify-center" style={{ backgroundColor: 'var(--bg-tertiary)' }}>
|
||
<div className="text-center">
|
||
<div className="text-6xl mb-2">😢</div>
|
||
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>No cards yet</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const mainCard = thumbnails[0];
|
||
const gridCards = thumbnails.slice(1, 5); // Get up to 4 cards for the 2x2 grid
|
||
|
||
console.log('🔍 CollectionThumbnail - mainCard:', mainCard);
|
||
console.log('🔍 CollectionThumbnail - gridCards:', gridCards);
|
||
|
||
return (
|
||
<div className="w-full h-48 rounded-xl overflow-hidden mb-4 p-3 flex gap-2" style={{ backgroundColor: 'var(--bg-tertiary)' }}>
|
||
{/* Main card (larger, left side) */}
|
||
<div className="flex-2 h-full">
|
||
{mainCard ? (
|
||
<div className="w-full h-full bg-white rounded-lg overflow-hidden shadow-sm border" style={{ borderColor: 'var(--border)' }}>
|
||
<img
|
||
src={mainCard.image_url || mainCard.stock_image_url}
|
||
alt={mainCard.name}
|
||
className="w-full h-full object-cover"
|
||
onError={(e) => console.log('🔍 Image load error:', e.target.src)}
|
||
onLoad={() => console.log('🔍 Image loaded successfully:', mainCard.image_url || mainCard.stock_image_url)}
|
||
/>
|
||
</div>
|
||
) : (
|
||
<div className="w-full h-full bg-white rounded-lg border" style={{ borderColor: 'var(--border)' }}></div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Grid of 4 smaller cards (right side) */}
|
||
<div className="flex-1 h-full">
|
||
<div className="grid grid-cols-2 gap-2 h-full">
|
||
{Array.from({ length: 4 }).map((_, index) => {
|
||
const card = gridCards[index];
|
||
return (
|
||
<div key={index} className="relative">
|
||
{card ? (
|
||
<div className="w-full h-full bg-white rounded-md overflow-hidden shadow-sm border" style={{ borderColor: 'var(--border)' }}>
|
||
<img
|
||
src={card.image_url || card.stock_image_url}
|
||
alt={card.name}
|
||
className="w-full h-full object-cover"
|
||
onError={(e) => console.log('🔍 Grid image load error:', e.target.src)}
|
||
onLoad={() => console.log('🔍 Grid image loaded successfully:', card.image_url || card.stock_image_url)}
|
||
/>
|
||
</div>
|
||
) : (
|
||
<div className="w-full h-full bg-white rounded-md border" style={{ borderColor: 'var(--border)' }}></div>
|
||
)}
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// Show loading spinner while auth is loading or data is loading
|
||
if (authLoading || loading) {
|
||
return (
|
||
<Layout user={user}>
|
||
<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(--accent-ember)' }}></div>
|
||
</div>
|
||
</Layout>
|
||
);
|
||
}
|
||
|
||
// Redirect to login if not authenticated (handled by useEffect, but this is a fallback)
|
||
if (!user) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<Layout user={user}>
|
||
{/* Header */}
|
||
<div className="p-6" style={{ backgroundColor: 'var(--bg-secondary)', borderBottom: '1px solid var(--border)' }}>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-3xl font-bold" style={{ color: 'var(--text-primary)' }}>
|
||
My Collections
|
||
</h1>
|
||
<p className="mt-2" style={{ color: 'var(--text-secondary)' }}>
|
||
Collections you own, collaborate on, or have been shared with you
|
||
</p>
|
||
</div>
|
||
<div className="flex gap-3">
|
||
<Link href="/community/collections">
|
||
<button className="px-4 py-2 rounded-xl border transition-colors hover:shadow-md"
|
||
style={{
|
||
borderColor: 'var(--border)',
|
||
color: 'var(--text-secondary)',
|
||
backgroundColor: 'transparent'
|
||
}}
|
||
>
|
||
🌍 Discover Community
|
||
</button>
|
||
</Link>
|
||
<button
|
||
onClick={() => setShowCreateModal(true)}
|
||
className="px-6 py-2 rounded-xl font-medium transition-all duration-200 hover:shadow-md"
|
||
style={{
|
||
backgroundColor: 'var(--accent-ember)',
|
||
color: 'white'
|
||
}}
|
||
>
|
||
+ Create Collection
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Filters and Search */}
|
||
<div className="p-6" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
||
<div className="flex flex-col md:flex-row gap-4">
|
||
<div className="flex-1">
|
||
<input
|
||
type="text"
|
||
placeholder="Search collections..."
|
||
className="input-field w-full"
|
||
value={searchQuery}
|
||
onChange={(e) => setSearchQuery(e.target.value)}
|
||
/>
|
||
</div>
|
||
<div className="flex gap-4">
|
||
<select
|
||
value={sortBy}
|
||
onChange={(e) => setSortBy(e.target.value)}
|
||
className="input-field w-48"
|
||
>
|
||
{sortOptions.map(option => (
|
||
<option key={option.value} value={option.value}>
|
||
{option.label}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Collections Grid */}
|
||
<div className="p-6">
|
||
{sortedCollections.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)' }}>
|
||
{searchQuery ? 'No collections found' : 'No collections yet'}
|
||
</h3>
|
||
<p className="mb-6" style={{ color: 'var(--text-secondary)' }}>
|
||
{searchQuery
|
||
? 'Try adjusting your search terms'
|
||
: 'Create your first collection to get started'
|
||
}
|
||
</p>
|
||
{!searchQuery && (
|
||
<button
|
||
onClick={() => setShowCreateModal(true)}
|
||
className="px-6 py-3 rounded-xl font-medium transition-all duration-200 hover:shadow-md"
|
||
style={{
|
||
backgroundColor: 'var(--accent-ember)',
|
||
color: 'white'
|
||
}}
|
||
>
|
||
Create Collection
|
||
</button>
|
||
)}
|
||
</div>
|
||
) : (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||
{sortedCollections.map(collection => (
|
||
<div
|
||
key={collection.id}
|
||
className="card hover:shadow-xl transition-all duration-300 cursor-pointer group"
|
||
onClick={() => router.push(`/collection/${collection.slug || collection.id}`)}
|
||
>
|
||
{/* Thumbnail Section */}
|
||
<CollectionThumbnail collection={collection} />
|
||
|
||
{/* Collection Info */}
|
||
<div className="space-y-3">
|
||
{/* Header with name and actions */}
|
||
<div className="flex items-start justify-between">
|
||
<div className="flex-1 min-w-0">
|
||
<h3 className="text-lg font-semibold truncate" style={{ color: 'var(--text-primary)' }}>
|
||
{collection.name}
|
||
</h3>
|
||
{collection.description && (
|
||
<p className="text-sm mt-1 line-clamp-2" style={{ color: 'var(--text-secondary)' }}>
|
||
{collection.description}
|
||
</p>
|
||
)}
|
||
</div>
|
||
<div className="flex items-center space-x-1 ml-2">
|
||
<PermissionIndicator
|
||
userRole={collection.userRole}
|
||
isPublic={collection.isPublic}
|
||
showTooltip={false}
|
||
/>
|
||
<div className="opacity-0 group-hover:opacity-100 transition-opacity flex space-x-1">
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
setEditingCollection(collection);
|
||
}}
|
||
className="p-1.5 rounded-lg transition-colors hover:scale-105"
|
||
style={{
|
||
backgroundColor: 'var(--bg-tertiary)',
|
||
color: 'var(--text-secondary)'
|
||
}}
|
||
>
|
||
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||
</svg>
|
||
</button>
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
handleDeleteCollection(collection.id);
|
||
}}
|
||
className="p-1.5 rounded-lg transition-colors hover:scale-105"
|
||
style={{
|
||
backgroundColor: 'var(--bg-tertiary)',
|
||
color: '#ef4444'
|
||
}}
|
||
>
|
||
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Compact Stats */}
|
||
<div className="flex items-center justify-between text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||
<div className="flex items-center space-x-4">
|
||
<span className="font-medium" style={{ color: 'var(--text-primary)' }}>
|
||
{collection.cardCount} cards
|
||
</span>
|
||
<span className="font-medium" style={{ color: 'var(--accent-ember)' }}>
|
||
{formatCurrency(collection.value)}
|
||
</span>
|
||
</div>
|
||
<span className="text-xs">
|
||
{formatDate(collection.createdAt)}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Tags */}
|
||
{collection.tags && collection.tags.length > 0 && (
|
||
<div className="flex flex-wrap gap-1">
|
||
{collection.tags.slice(0, 2).map(tag => (
|
||
<span
|
||
key={tag}
|
||
className="px-2 py-1 text-xs rounded-full"
|
||
style={{
|
||
backgroundColor: 'var(--bg-tertiary)',
|
||
color: 'var(--text-secondary)'
|
||
}}
|
||
>
|
||
{tag}
|
||
</span>
|
||
))}
|
||
{collection.tags.length > 2 && (
|
||
<span className="px-2 py-1 text-xs rounded-full" style={{
|
||
backgroundColor: 'var(--bg-tertiary)',
|
||
color: 'var(--text-secondary)'
|
||
}}>
|
||
+{collection.tags.length - 2}
|
||
</span>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Create Collection Modal */}
|
||
{showCreateModal && (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div className="card max-w-md w-full mx-4">
|
||
<h2 className="text-2xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||
Create New Collection
|
||
</h2>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
||
Collection Name
|
||
</label>
|
||
<input
|
||
type="text"
|
||
className="input-field w-full"
|
||
value={newCollection.name}
|
||
onChange={(e) => setNewCollection({...newCollection, name: e.target.value})}
|
||
placeholder="Enter collection name"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
||
Description
|
||
</label>
|
||
<textarea
|
||
className="input-field w-full"
|
||
rows="3"
|
||
value={newCollection.description}
|
||
onChange={(e) => setNewCollection({...newCollection, description: e.target.value})}
|
||
placeholder="Describe your collection"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
||
Hero Image (Optional)
|
||
</label>
|
||
<input
|
||
type="url"
|
||
className="input-field w-full"
|
||
value={newCollection.image}
|
||
onChange={(e) => setNewCollection({...newCollection, image: e.target.value})}
|
||
placeholder="Enter image URL or upload later"
|
||
/>
|
||
<p className="text-xs mt-1" style={{ color: 'var(--text-secondary)' }}>
|
||
Add a custom thumbnail image, or we'll use your rarest cards
|
||
</p>
|
||
</div>
|
||
|
||
{/* Tags Section */}
|
||
<div>
|
||
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
||
Tags (Optional)
|
||
</label>
|
||
<div className="space-y-2">
|
||
<div className="flex gap-2">
|
||
<input
|
||
type="text"
|
||
className="input-field flex-1"
|
||
value={tagInput}
|
||
onChange={(e) => setTagInput(e.target.value)}
|
||
onKeyPress={(e) => handleTagKeyPress(e, tagInput, setTagInput, newCollection, setNewCollection)}
|
||
placeholder="Add tags (press Enter)"
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={() => handleAddTag(tagInput, setTagInput, newCollection, setNewCollection)}
|
||
className="px-3 py-2 rounded-lg text-sm font-medium transition-colors"
|
||
style={{
|
||
backgroundColor: 'var(--accent-ember)',
|
||
color: 'white'
|
||
}}
|
||
disabled={!tagInput.trim()}
|
||
>
|
||
Add
|
||
</button>
|
||
</div>
|
||
{newCollection.tags.length > 0 && (
|
||
<div className="flex flex-wrap gap-2">
|
||
{newCollection.tags.map((tag, index) => (
|
||
<span
|
||
key={index}
|
||
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium"
|
||
style={{
|
||
backgroundColor: 'var(--bg-tertiary)',
|
||
color: 'var(--text-primary)',
|
||
border: '1px solid var(--border)'
|
||
}}
|
||
>
|
||
{tag}
|
||
<button
|
||
type="button"
|
||
onClick={() => handleRemoveTag(tag, newCollection, setNewCollection)}
|
||
className="ml-1 hover:text-red-500 transition-colors"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
)}
|
||
<p className="text-xs" style={{ color: 'var(--text-secondary)' }}>
|
||
Add tags to help organize and categorize your collection
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between p-4 rounded-lg border" style={{ borderColor: 'var(--border)' }}>
|
||
<div>
|
||
<label className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>
|
||
Public Collection
|
||
</label>
|
||
<p className="text-xs mt-1" style={{ color: 'var(--text-secondary)' }}>
|
||
Make this collection discoverable by other users
|
||
</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => setNewCollection({...newCollection, isPublic: !newCollection.isPublic})}
|
||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||
newCollection.isPublic ? 'bg-green-600' : 'bg-gray-300 dark:bg-gray-600'
|
||
}`}
|
||
>
|
||
<span
|
||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||
newCollection.isPublic ? 'translate-x-6' : 'translate-x-1'
|
||
}`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div className="flex space-x-3 mt-6">
|
||
<button
|
||
onClick={() => {
|
||
setShowCreateModal(false);
|
||
setTagInput(''); // Clear tag input when closing
|
||
}}
|
||
className="flex-1 py-2 px-4 rounded-xl border transition-colors"
|
||
style={{
|
||
borderColor: 'var(--border)',
|
||
color: 'var(--text-secondary)'
|
||
}}
|
||
>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
onClick={handleCreateCollection}
|
||
disabled={!newCollection.name.trim()}
|
||
className="flex-1 py-2 px-4 rounded-xl font-medium transition-all duration-200 hover:shadow-md disabled:opacity-50"
|
||
style={{
|
||
backgroundColor: 'var(--accent-ember)',
|
||
color: 'white'
|
||
}}
|
||
>
|
||
Create Collection
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Success Modal */}
|
||
{showSuccessModal && createdCollection && (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div className="card max-w-md w-full mx-4 text-center">
|
||
<div className="text-6xl mb-4">🎉</div>
|
||
<h2 className="text-2xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||
Collection Created!
|
||
</h2>
|
||
<p className="mb-6" style={{ color: 'var(--text-secondary)' }}>
|
||
Your collection "{createdCollection.name}" has been created successfully.
|
||
</p>
|
||
<div className="space-y-3">
|
||
<button
|
||
onClick={() => {
|
||
setShowSuccessModal(false);
|
||
router.push(`/collection/${createdCollection.slug || createdCollection.id}`);
|
||
}}
|
||
className="w-full py-2 px-4 rounded-xl font-medium transition-all duration-200 hover:shadow-md"
|
||
style={{
|
||
backgroundColor: 'var(--accent-ember)',
|
||
color: 'white'
|
||
}}
|
||
>
|
||
View Collection
|
||
</button>
|
||
<button
|
||
onClick={() => setShowSuccessModal(false)}
|
||
className="w-full py-2 px-4 rounded-xl border transition-colors"
|
||
style={{
|
||
borderColor: 'var(--border)',
|
||
color: 'var(--text-secondary)'
|
||
}}
|
||
>
|
||
Stay on Collections Page
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Edit Collection Modal */}
|
||
{editingCollection && (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div className="card max-w-md w-full mx-4">
|
||
<h2 className="text-2xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||
Edit Collection
|
||
</h2>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
||
Collection Name
|
||
</label>
|
||
<input
|
||
type="text"
|
||
className="input-field w-full"
|
||
value={editingCollection.name}
|
||
onChange={(e) => setEditingCollection({...editingCollection, name: e.target.value})}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
||
Description
|
||
</label>
|
||
<textarea
|
||
className="input-field w-full"
|
||
rows="3"
|
||
value={editingCollection.description}
|
||
onChange={(e) => setEditingCollection({...editingCollection, description: e.target.value})}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
||
Hero Image
|
||
</label>
|
||
<input
|
||
type="url"
|
||
className="input-field w-full"
|
||
value={editingCollection.image || ''}
|
||
onChange={(e) => setEditingCollection({...editingCollection, image: e.target.value})}
|
||
placeholder="Enter image URL"
|
||
/>
|
||
</div>
|
||
|
||
{/* Tags Section */}
|
||
<div>
|
||
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
||
Tags
|
||
</label>
|
||
<div className="space-y-2">
|
||
<div className="flex gap-2">
|
||
<input
|
||
type="text"
|
||
className="input-field flex-1"
|
||
value={editTagInput}
|
||
onChange={(e) => setEditTagInput(e.target.value)}
|
||
onKeyPress={(e) => handleTagKeyPress(e, editTagInput, setEditTagInput, editingCollection, setEditingCollection)}
|
||
placeholder="Add tags (press Enter)"
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={() => handleAddTag(editTagInput, setEditTagInput, editingCollection, setEditingCollection)}
|
||
className="px-3 py-2 rounded-lg text-sm font-medium transition-colors"
|
||
style={{
|
||
backgroundColor: 'var(--accent-ember)',
|
||
color: 'white'
|
||
}}
|
||
disabled={!editTagInput.trim()}
|
||
>
|
||
Add
|
||
</button>
|
||
</div>
|
||
{editingCollection.tags && editingCollection.tags.length > 0 && (
|
||
<div className="flex flex-wrap gap-2">
|
||
{editingCollection.tags.map((tag, index) => (
|
||
<span
|
||
key={index}
|
||
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium"
|
||
style={{
|
||
backgroundColor: 'var(--bg-tertiary)',
|
||
color: 'var(--text-primary)',
|
||
border: '1px solid var(--border)'
|
||
}}
|
||
>
|
||
{tag}
|
||
<button
|
||
type="button"
|
||
onClick={() => handleRemoveTag(tag, editingCollection, setEditingCollection)}
|
||
className="ml-1 hover:text-red-500 transition-colors"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between p-4 rounded-lg border" style={{ borderColor: 'var(--border)' }}>
|
||
<div>
|
||
<label className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>
|
||
Public Collection
|
||
</label>
|
||
<p className="text-xs mt-1" style={{ color: 'var(--text-secondary)' }}>
|
||
Make this collection discoverable by other users
|
||
</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => setEditingCollection({...editingCollection, isPublic: !editingCollection.isPublic})}
|
||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||
editingCollection.isPublic ? 'bg-green-600' : 'bg-gray-300 dark:bg-gray-600'
|
||
}`}
|
||
>
|
||
<span
|
||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||
editingCollection.isPublic ? 'translate-x-6' : 'translate-x-1'
|
||
}`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div className="flex space-x-3 mt-6">
|
||
<button
|
||
onClick={() => {
|
||
setEditingCollection(null);
|
||
setEditTagInput(''); // Clear tag input when closing
|
||
}}
|
||
className="flex-1 py-2 px-4 rounded-xl border transition-colors"
|
||
style={{
|
||
borderColor: 'var(--border)',
|
||
color: 'var(--text-secondary)'
|
||
}}
|
||
>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
onClick={handleUpdateCollection}
|
||
disabled={!editingCollection.name.trim()}
|
||
className="flex-1 py-2 px-4 rounded-xl font-medium transition-all duration-200 hover:shadow-md disabled:opacity-50"
|
||
style={{
|
||
backgroundColor: 'var(--accent-ember)',
|
||
color: 'white'
|
||
}}
|
||
>
|
||
Update Collection
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</Layout>
|
||
);
|
||
}
|