Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | 23x 23x 23x 23x 23x 10x 13x 2x 1x 11x 2x 11x 10x 10x 1x | import { useNavigate, useParams } from 'react-router-dom'
import { Trash2, X, FolderOpen } from 'lucide-react'
import { type CollectionDetail as CollectionDetailType, type Recipe } from '../api/client'
import { useCollectionData } from '../hooks/useCollectionData'
import NavHeader from '../components/NavHeader'
import RecipeCard from '../components/RecipeCard'
import DeleteConfirmBanner from '../components/DeleteConfirmBanner'
import { LoadingSpinner } from '../components/Skeletons'
export default function CollectionDetail() {
const navigate = useNavigate()
const { id } = useParams<{ id: string }>()
const collectionId = Number(id)
const {
collection,
loading,
showDeleteConfirm,
setShowDeleteConfirm,
deleting,
handleRemoveRecipe,
handleDeleteCollection,
handleRecipeClick,
} = useCollectionData(collectionId)
if (loading) {
return (
<div className="min-h-screen bg-background">
<LoadingSpinner className="min-h-screen" />
</div>
)
}
if (!collection) {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-background">
<span className="mb-4 text-muted-foreground">Collection not found</span>
<button onClick={() => navigate('/collections')} className="rounded-lg bg-primary px-4 py-2 text-primary-foreground">
Go Back
</button>
</div>
)
}
return (
<div className="min-h-screen bg-background">
<NavHeader />
<main className="px-4 py-6">
<div className="mx-auto max-w-4xl">
<CollectionHeader name={collection.name} recipeCount={collection.recipes.length} onDelete={() => setShowDeleteConfirm(true)} />
{showDeleteConfirm && (
<DeleteConfirmBanner itemName={collection.name} deleting={deleting} onConfirm={handleDeleteCollection} onCancel={() => setShowDeleteConfirm(false)} />
)}
{collection.recipes.length > 0 ? (
<CollectionRecipeGrid recipes={collection.recipes} onRecipeClick={handleRecipeClick} onRemoveRecipe={handleRemoveRecipe} />
) : (
<EmptyCollection />
)}
</div>
</main>
</div>
)
}
function CollectionHeader({ name, recipeCount, onDelete }: { name: string; recipeCount: number; onDelete: () => void }) {
return (
<div className="mb-4 flex items-center justify-between">
<div>
<h2 className="text-lg font-medium text-foreground">{name}</h2>
<p className="text-sm text-muted-foreground">{recipeCount} recipe{recipeCount !== 1 ? 's' : ''}</p>
</div>
<button onClick={onDelete} className="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive" title="Delete collection">
<Trash2 className="h-5 w-5" />
</button>
</div>
)
}
function CollectionRecipeGrid({
recipes,
onRecipeClick,
onRemoveRecipe,
}: {
recipes: CollectionDetailType['recipes']
onRecipeClick: (id: number) => void
onRemoveRecipe: (recipe: Recipe) => void
}) {
return (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4">
{recipes.map((item) => (
<div key={item.recipe.id} className="group relative">
<RecipeCard recipe={item.recipe} onClick={() => onRecipeClick(item.recipe.id)} />
<button
onClick={(e) => { e.stopPropagation(); onRemoveRecipe(item.recipe) }}
className="absolute right-2 top-2 rounded-full bg-destructive/90 p-1.5 text-destructive-foreground opacity-0 transition-opacity group-hover:opacity-100"
title="Remove from collection"
>
<X className="h-4 w-4" />
</button>
</div>
))}
</div>
)
}
function EmptyCollection() {
return (
<div className="flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-border py-12">
<div className="mb-4 rounded-full bg-muted p-4">
<FolderOpen className="h-8 w-8 text-muted-foreground" />
</div>
<h3 className="mb-2 text-lg font-medium text-foreground">This collection is empty</h3>
<p className="text-center text-muted-foreground">Add recipes from their detail pages.</p>
</div>
)
}
|