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 | 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 11x 11x 11x 11x 11x 9x 9x 9x 9x 1x 1x 1x 10x 11x 30x 30x 30x 30x 30x 30x 1x 1x | import { useState, useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { toast } from 'sonner'
import { api, type RecipeDetail as RecipeDetailType } from '../api/client'
import { useProfile } from '../contexts/ProfileContext'
import { useAIStatus } from '../contexts/AIStatusContext'
import { useTipsPolling } from './useTipsPolling'
import { useServingScale } from './useServingScale'
type Tab = 'ingredients' | 'instructions' | 'nutrition' | 'tips'
export function useRecipeDetail() {
const navigate = useNavigate()
const { id } = useParams<{ id: string }>()
const recipeId = Number(id)
const { profile, isFavorite, toggleFavorite } = useProfile()
const aiStatus = useAIStatus()
const [recipe, setRecipe] = useState<RecipeDetailType | null>(null)
const [loading, setLoading] = useState(true)
const [activeTab, setActiveTab] = useState<Tab>('ingredients')
const [metaExpanded, setMetaExpanded] = useState(true)
const [showRemixModal, setShowRemixModal] = useState(false)
const { servings, scaledData, scalingLoading, setServings, setScaledData, handleServingChange } =
useServingScale(recipe, profile?.id)
const { tips, tipsLoading, tipsPolling, handleGenerateTips } = useTipsPolling({
recipe,
aiAvailable: aiStatus.available,
activeTab,
setRecipe,
})
useEffect(() => {
Iif (!recipeId) return
let cancelled = false
;(async () => {
try {
const recipeData = await api.recipes.get(recipeId)
Eif (!cancelled) {
setRecipe(recipeData)
setServings(recipeData.servings)
setScaledData(null)
}
} catch (error) {
Eif (!cancelled) {
console.error('Failed to load recipe:', error)
toast.error('Failed to load recipe')
}
} finally {
Eif (!cancelled) setLoading(false)
}
})()
return () => { cancelled = true }
// eslint-disable-next-line react-hooks/exhaustive-deps -- only re-run when recipeId changes
}, [recipeId])
const canShowServingAdjustment = aiStatus.available && recipe?.servings !== null
const handleFavoriteToggle = async () => {
if (!recipe) return
await toggleFavorite(recipe)
}
const handleRemixCreated = async (newRecipeId: number) => {
try { await api.history.record(newRecipeId) } catch (error) {
console.error('Failed to record history:', error)
}
navigate(`/recipe/${newRecipeId}`)
}
const recipeIsFavorite = recipe ? isFavorite(recipe.id) : false
const imageUrl = recipe ? (recipe.image || recipe.image_url) : null
return {
recipe, loading, activeTab, setActiveTab, metaExpanded, setMetaExpanded,
servings, showRemixModal, setShowRemixModal, scaledData, scalingLoading,
tips, tipsLoading, tipsPolling, profile, aiStatus, recipeId,
canShowServingAdjustment, recipeIsFavorite, imageUrl,
handleServingChange, handleGenerateTips, handleFavoriteToggle,
handleStartCooking: () => navigate(`/recipe/${recipeId}/play`),
handleAddToNewCollection: () => navigate(`/collections?addRecipe=${recipeId}`),
handleRemixCreated,
handleBack: () => navigate(-1),
}
}
export type { Tab }
|