deckhearth/pages/collections.js
varutasu c32bbd19b6
fix(lint): clear ESLint baseline in pages/ (#62)
Resolve react-hooks, no-unescaped-entities, and no-img-element findings
under pages/ with lint-only changes so npx eslint pages/ exits clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:03:39 -05:00

994 lines
No EOL
40 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint-disable @next/next/no-img-element -- External or generated image URLs; next/image migration is out of scope. */
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';
import { VOCAB, collectionDisplayName } from '../lib/collection-vocabulary.js';
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]);
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);
}
}
useEffect(() => {
if (user) {
// eslint-disable-next-line react-hooks/set-state-in-effect -- load lists when user is available
fetchCollections();
}
}, [user]);
;
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 list');
}
} 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 list');
}
} 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 list? 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 list');
}
} 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 - Updated design with card images
const CollectionThumbnail = ({ collection }) => {
const { thumbnails = [], image } = collection;
// If there's a custom image, show it
if (image) {
return (
<div className="w-full h-48 rounded-xl overflow-hidden mb-4 relative">
<img
src={image}
alt={collection.name}
className="w-full h-full object-cover"
/>
{/* Floating badges over custom image */}
<div className="absolute top-3 right-3 flex gap-2">
{collection.userRole === 'owner' && (
<span className="px-2 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800 backdrop-blur-sm bg-opacity-90">
👑 Owner
</span>
)}
{collection.isPublic && (
<span className="px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 backdrop-blur-sm bg-opacity-90">
🌍 Public
</span>
)}
</div>
</div>
);
}
// If no cards, show crying emoji
if (!thumbnails || thumbnails.length === 0) {
return (
<div className="w-full h-48 rounded-xl mb-4 flex items-center justify-center relative" 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>
{/* Floating badges over empty state */}
<div className="absolute top-3 right-3 flex gap-2">
{collection.userRole === 'owner' && (
<span className="px-2 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800 backdrop-blur-sm bg-opacity-90">
👑 Owner
</span>
)}
{collection.isPublic && (
<span className="px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 backdrop-blur-sm bg-opacity-90">
🌍 Public
</span>
)}
</div>
</div>
);
}
const mainCard = thumbnails[0];
const gridCards = thumbnails.slice(1, 5); // Get up to 4 cards for the 2x2 grid
return (
<div className="w-full h-48 rounded-xl overflow-hidden mb-4 p-3 flex gap-2 relative" 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"
/>
</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"
/>
</div>
) : (
<div className="w-full h-full bg-white rounded-md border" style={{ borderColor: 'var(--border)' }}></div>
)}
</div>
);
})}
</div>
</div>
{/* Floating badges over card layout */}
<div className="absolute top-3 right-3 flex gap-2">
{collection.userRole === 'owner' && (
<span className="px-2 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800 backdrop-blur-sm bg-opacity-90">
👑 Owner
</span>
)}
{collection.isPublic && (
<span className="px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 backdrop-blur-sm bg-opacity-90">
🌍 Public
</span>
)}
</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)' }}>
{VOCAB.LISTS}
</h1>
<p className="mt-2" style={{ color: 'var(--text-secondary)' }}>
Lists 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 List
</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 lists..."
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 lists found' : 'No lists yet'}
</h3>
<p className="mb-6" style={{ color: 'var(--text-secondary)' }}>
{searchQuery
? 'Try adjusting your search terms'
: 'Create your first list 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 List
</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 description - more space */}
<div className="space-y-2">
<div className="flex items-start justify-between">
<div className="flex items-center space-x-2">
<h3 className="text-lg font-semibold leading-tight" style={{ color: 'var(--text-primary)' }}>
{collectionDisplayName(collection)}
</h3>
{/* System collection indicator */}
{collection.isSystemCollection && (
<div className="flex items-center space-x-1">
<span className="px-2.5 py-1 text-xs font-bold rounded-full bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-sm border-2 border-blue-200">
🔒 SYSTEM
</span>
<div className="group relative">
<svg className="h-4 w-4 text-blue-500 cursor-help" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 text-xs bg-gray-900 text-white rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity z-10 whitespace-nowrap">
{VOCAB.SYSTEM_COLLECTION_SYNC_HINT}
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div>
</div>
</div>
</div>
)}
</div>
{/* Edit/Delete buttons - hidden for system collections */}
{!collection.isSystemCollection && (
<div className="opacity-0 group-hover:opacity-100 transition-opacity flex space-x-1 ml-2 flex-shrink-0">
<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>
{collection.description && (
<p className="text-sm leading-relaxed" style={{ color: 'var(--text-secondary)' }}>
{collection.description}
</p>
)}
</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>
)}
{/* Creator/Facepile and View Button Row */}
<div className="flex items-center justify-between pt-2 border-t" style={{ borderColor: 'var(--border)' }}>
<div className="flex items-center space-x-2">
{/* Creator info or facepile */}
<div className="flex items-center space-x-1">
<div className="w-6 h-6 rounded-full bg-gradient-to-r from-orange-400 to-pink-400 flex items-center justify-center text-xs font-bold text-white">
{collection.creator ? collection.creator.charAt(0).toUpperCase() : 'A'}
</div>
<span className="text-xs" style={{ color: 'var(--text-secondary)' }}>
{collection.creator ? collection.creator.split('@')[0] : 'alice'}
</span>
</div>
{/* Additional collaborators could go here as overlapping avatars */}
</div>
<button
onClick={(e) => {
e.stopPropagation();
router.push(`/collection/${collection.slug || collection.id}`);
}}
className="px-3 py-1.5 text-xs font-medium rounded-lg transition-colors hover:scale-105"
style={{
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-primary)',
border: '1px solid var(--border)'
}}
>
View
</button>
</div>
</div>
</div>
))}
</div>
)}
</div>
{/* Create List 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 List
</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
List Name
</label>
<input
type="text"
className="input-field w-full"
value={newCollection.name}
onChange={(e) => setNewCollection({...newCollection, name: e.target.value})}
placeholder="Enter list 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 list"
/>
</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&apos;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 list
</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 List
</label>
<p className="text-xs mt-1" style={{ color: 'var(--text-secondary)' }}>
Make this list 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 List
</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)' }}>
List Created!
</h2>
<p className="mb-6" style={{ color: 'var(--text-secondary)' }}>
Your list &quot;{createdCollection.name}&quot; 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 List
</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 Lists 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 List
</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
List 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 List
</label>
<p className="text-xs mt-1" style={{ color: 'var(--text-secondary)' }}>
Make this list 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 List
</button>
</div>
</div>
</div>
)}
</Layout>
);
}