src/screens/Favorites.tsx (Line 29:6 - Line 44:7), src/screens/Home.tsx (Line 107:2 - Line 122:6)
)
}
}
const handleRemoveFavorite = async (recipe: Recipe) => {
try {
await api.favorites.remove(recipe.id)
setFavorites(favorites.filter((f) => f.recipe.id !== recipe.id))
toast.success('Removed from favorites')
} catch (error) {
console.error('Failed to remove favorite:', error)
toast.error('Failed to remove from favorites')
}
}
return
src/screens/CollectionDetail.tsx (Line 91:5 - Line 105:4), src/screens/Collections.tsx (Line 89:13 - Line 103:3)
)
}
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="flex items-center justify-between border-b border-border px-4 py-3">
<div className="flex items-center gap-4">
<button
onClick={onBack}
className="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
>
<ArrowLeft className="h-5 w-5" />
</button>
<div
src/screens/AllRecipes.tsx (Line 30:6 - Line 44:4), src/screens/Favorites.tsx (Line 40:34 - Line 54:10)
)
}
}
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="flex items-center gap-4 border-b border-border px-4 py-3">
<button
onClick={onBack}
className="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
>
<ArrowLeft className="h-5 w-5" />
</button>
<h1 className="text-xl font-medium text-foreground">All
src/components/Skeletons.tsx (Line 169:21 - Line 178:11), src/components/Skeletons.tsx (Line 20:19 - Line 29:15)
() {
return (
<div className="overflow-hidden rounded-lg bg-card shadow-sm">
{/* Image placeholder */}
<Skeleton className="aspect-[4/3] rounded-none" />
{/* Content */}
<div className="p-3 space-y-2">
{/* Title */}
<Skeleton className="h-4 w-3/4" />
{/* Meta */
src/components/RecipeCard.tsx (Line 20:3 - Line 28:20), src/screens/RecipeDetail.tsx (Line 141:3 - Line 149:25)
const formatTime = (minutes: number | null) => {
if (!minutes) return null
if (minutes < 60) return `${minutes} min`
const hours = Math.floor(minutes / 60)
const mins = minutes % 60
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`
}
const handleFavoriteClick
src/components/RecipeCard.tsx (Line 40:5 - Line 55:2), src/screens/Search.tsx (Line 257:2 - Line 271:3)
>
{/* Image */}
<div className="relative aspect-[4/3] overflow-hidden bg-muted">
{imageUrl ? (
<img
src={imageUrl}
alt={recipe.title}
className="h-full w-full object-cover transition-transform group-hover:scale-105"
/>
) : (
<div className="flex h-full w-full items-center justify-center text-muted-foreground">
No image
</div>
)}
{
src/hooks/useTimers.ts (Line 91:5 - Line 114:10), src/hooks/useTimers.ts (Line 45:7 - Line 67:2)
const intervalId = window.setInterval(() => {
setTimers((prev) =>
prev.map((timer) => {
if (timer.id !== id) return timer
if (!timer.isRunning) return timer
const newRemaining = timer.remaining - 1
if (newRemaining <= 0) {
// Timer completed
clearInterval(intervalsRef.current.get(id))
intervalsRef.current.delete(id)
onCompleteRef.current?.({ ...timer, remaining: 0, isRunning: false })
return { ...timer, remaining: 0, isRunning: false }
}
return { ...timer, remaining: newRemaining }
})
)
}, 1000)
intervalsRef.current.set(id, intervalId)
setTimers
src/hooks/useTimers.ts (Line 136:2 - Line 147:10), src/hooks/useTimers.ts (Line 121:2 - Line 131:10)
= useCallback((id: string) => {
// Clear interval
const intervalId = intervalsRef.current.get(id)
if (intervalId) {
clearInterval(intervalId)
intervalsRef.current.delete(id)
}
setTimers((prev) =>
prev.map((timer) =>
timer.id === id
? { ...timer, remaining