All files / src/hooks useRecipeDetail.ts

81.25% Statements 39/48
65% Branches 13/20
66.66% Functions 6/9
86.04% Lines 37/43

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                        30x 30x 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, profile?.unit_preference ?? 'metric')
 
  const tipsAvailable = aiStatus.isFeatureAvailable('tips')
  const remixAvailable = aiStatus.isFeatureAvailable('remix')
 
  const { tips, tipsLoading, tipsPolling, handleGenerateTips } = useTipsPolling({
    recipe,
    aiAvailable: tipsAvailable,
    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.isFeatureAvailable('scale') && 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,
    tipsAvailable, remixAvailable,
    handleServingChange, handleGenerateTips, handleFavoriteToggle,
    handleStartCooking: () => navigate(`/recipe/${recipeId}/play`),
    handleAddToNewCollection: () => navigate(`/collections?addRecipe=${recipeId}`),
    handleRemixCreated,
    handleBack: () => navigate(-1),
  }
}
 
export type { Tab }
 
← Back to Dashboard