import React, { useState, useEffect, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useAuth } from '../auth'; import { getCollectionDetail, getCollectionByShareToken, createBoard, deleteBoard as apiDeleteBoard, } from '../api'; import ShareDialog from '../components/ShareDialog'; interface Board { id: string; name: string; description: string; image_count: number; created_at: string; updated_at: string; } interface CollectionDetailProps { isPublicView?: boolean; } const gradients = [ 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)', 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', 'linear-gradient(135deg, #89f7fe 0%, #66a6ff 100%)', ]; function hashCode(str: string): number { let hash = 0; for (let i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); hash |= 0; } return Math.abs(hash); } export default function CollectionDetail({ isPublicView }: CollectionDetailProps) { const { collectionId, shareToken } = useParams<{ collectionId?: string; shareToken?: string }>(); const navigate = useNavigate(); const { user } = useAuth(); const [collection, setCollection] = useState(null); const [boards, setBoards] = useState([]); const [members, setMembers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [showModal, setShowModal] = useState(false); const [showShare, setShowShare] = useState(false); const [newName, setNewName] = useState(''); const [newDesc, setNewDesc] = useState(''); const [creating, setCreating] = useState(false); const load = useCallback(async () => { try { let res; if (shareToken) { res = await getCollectionByShareToken(shareToken); setCollection(res.data.collection); setBoards(res.data.boards || []); setMembers([]); } else if (collectionId) { res = await getCollectionDetail(collectionId); setCollection(res.data.collection); setBoards(res.data.boards || []); setMembers(res.data.members || []); } setLoading(false); } catch (err: any) { setError(err.response?.data?.error || 'Failed to load collection'); setLoading(false); } }, [collectionId, shareToken]); useEffect(() => { load(); }, [load]); const resolvedId = collectionId || collection?.id; const isOwner = members.some((m: any) => m.user_id === user?.id && m.role === 'owner'); const isEditor = isOwner || members.some((m: any) => m.user_id === user?.id && (m.role === 'editor' || m.role === 'owner')); async function handleCreateBoard() { if (!newName.trim() || !resolvedId) return; setCreating(true); try { const res = await createBoard(resolvedId, newName.trim(), newDesc.trim()); const board = res.data.board || res.data; setShowModal(false); setNewName(''); setNewDesc(''); navigate(`/board/${board.id}`); } catch (err) { console.error('Failed to create board:', err); } finally { setCreating(false); } } async function handleDeleteBoard(e: React.MouseEvent, boardId: string) { e.stopPropagation(); if (!confirm('Delete this board? This cannot be undone.')) return; try { await apiDeleteBoard(boardId); setBoards((prev) => prev.filter((b) => b.id !== boardId)); } catch (err) { console.error('Failed to delete board:', err); } } if (loading) { return (
Loading...
); } if (error) { return (
{error}
); } return (
{/* Header */}
{collection?.name || 'Collection'}
{user && !isPublicView && ( )}
{/* Controls */}
{isEditor && ( )} {collection?.description && ( {collection.description} )}
{/* Grid */}
{boards.length === 0 && (
+
No boards in this collection yet.
)} {boards.map((board) => (
navigate(`/board/${board.id}`)} style={{ background: '#141414', borderRadius: '12px', border: '1px solid #1e1e1e', overflow: 'hidden', cursor: 'pointer', transition: 'all 0.2s ease', }} onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.borderColor = '#333'; (e.currentTarget as HTMLElement).style.transform = 'translateY(-2px)'; (e.currentTarget as HTMLElement).style.boxShadow = '0 8px 24px rgba(0,0,0,0.3)'; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.borderColor = '#1e1e1e'; (e.currentTarget as HTMLElement).style.transform = 'none'; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }} >
{board.name.charAt(0).toUpperCase()}

{board.name}

{board.image_count} img{board.image_count !== 1 ? 's' : ''} {new Date(board.updated_at).toLocaleDateString()} {isOwner && ( )}
))}
{/* Create board modal */} {showModal && (
setShowModal(false)}>
e.stopPropagation()}>

New Board

setNewName(e.target.value)} autoFocus onKeyDown={(e) => { if (e.key === 'Enter') handleCreateBoard(); }} style={{ width: '100%', padding: '10px 14px', marginBottom: '12px', background: '#0d0d0d', border: '1px solid #2a2a2a', borderRadius: '8px', color: '#e0e0e0', fontSize: '14px', outline: 'none', boxSizing: 'border-box', }} /> setNewDesc(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleCreateBoard(); }} style={{ width: '100%', padding: '10px 14px', marginBottom: '16px', background: '#0d0d0d', border: '1px solid #2a2a2a', borderRadius: '8px', color: '#e0e0e0', fontSize: '14px', outline: 'none', boxSizing: 'border-box', }} />
)} {showShare && resolvedId && ( setShowShare(false)} /> )}
); }