From 299a83a38b5fd743e03f1d5ed4cf52f2bb1e7db3 Mon Sep 17 00:00:00 2001 From: Zach Duey Date: Sat, 18 Apr 2026 21:11:20 -0500 Subject: [PATCH] implement mvp 'cook' mode --- .../frontend/src/components/CookMode.tsx | 205 ++++++++++++++++++ .../src/components/SavedRecipeView.tsx | 26 +++ 2 files changed, 231 insertions(+) create mode 100644 apps/kitchen_mate/frontend/src/components/CookMode.tsx diff --git a/apps/kitchen_mate/frontend/src/components/CookMode.tsx b/apps/kitchen_mate/frontend/src/components/CookMode.tsx new file mode 100644 index 0000000..bcbf6b3 --- /dev/null +++ b/apps/kitchen_mate/frontend/src/components/CookMode.tsx @@ -0,0 +1,205 @@ +import { useState, useRef } from "react"; +import { Recipe } from "../types/recipe"; + +interface CookModeProps { + recipe: Recipe; + onClose: () => void; +} + +export function CookMode({ recipe, onClose }: CookModeProps) { + const [activeTab, setActiveTab] = useState<"ingredients" | "instructions">("ingredients"); + const [checkedIngredients, setCheckedIngredients] = useState>(new Set()); + const [currentStep, setCurrentStep] = useState(0); + + const touchStartX = useRef(null); + + const totalSteps = recipe.instructions.length; + const checkedCount = checkedIngredients.size; + + const toggleIngredient = (index: number) => { + setCheckedIngredients((prev) => { + const next = new Set(prev); + if (next.has(index)) { + next.delete(index); + } else { + next.add(index); + } + return next; + }); + }; + + const goNext = () => setCurrentStep((s) => Math.min(s + 1, totalSteps - 1)); + const goPrev = () => setCurrentStep((s) => Math.max(s - 1, 0)); + + const handleTouchStart = (e: React.TouchEvent) => { + touchStartX.current = e.touches[0].clientX; + }; + + const handleTouchEnd = (e: React.TouchEvent) => { + if (touchStartX.current === null) return; + const delta = touchStartX.current - e.changedTouches[0].clientX; + if (Math.abs(delta) > 50) { + if (delta > 0) goNext(); + else goPrev(); + } + touchStartX.current = null; + }; + + return ( +
+ {/* Header */} +
+ +

+ {recipe.title} +

+
+ + {/* Tabs */} +
+ + +
+ + {/* Content */} + {activeTab === "ingredients" ? ( +
+
    + {recipe.ingredients.map((ingredient, index) => { + const checked = checkedIngredients.has(index); + const text = ingredient.display_text || ingredient.name; + return ( +
  • + +
  • + ); + })} +
+ {/* Progress footer */} +
+ {checkedCount === recipe.ingredients.length && checkedCount > 0 + ? "All ingredients gathered!" + : `${checkedCount} of ${recipe.ingredients.length} gathered`} +
+
+ ) : ( +
+ {/* Progress indicator */} +
+

+ Step {currentStep + 1} of {totalSteps} +

+
+ {recipe.instructions.map((_, i) => ( +
+
+ + {/* Step text */} +
+

+ {recipe.instructions[currentStep]} +

+
+ + {/* Navigation */} +
+ + {currentStep < totalSteps - 1 ? ( + + ) : ( + + )} +
+
+ )} +
+ ); +} diff --git a/apps/kitchen_mate/frontend/src/components/SavedRecipeView.tsx b/apps/kitchen_mate/frontend/src/components/SavedRecipeView.tsx index b3aec0e..6679eaa 100644 --- a/apps/kitchen_mate/frontend/src/components/SavedRecipeView.tsx +++ b/apps/kitchen_mate/frontend/src/components/SavedRecipeView.tsx @@ -14,6 +14,7 @@ import { ExportDropdown } from "./ExportDropdown"; import { LoadingSpinner } from "./LoadingSpinner"; import { ShareButton } from "./ShareButton"; import { ShareToKitchenButton } from "./ShareToKitchenButton"; +import { CookMode } from "./CookMode"; export function SavedRecipeView() { const { id } = useParams<{ id: string }>(); @@ -26,6 +27,7 @@ export function SavedRecipeView() { const [isSaving, setIsSaving] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [cookModeOpen, setCookModeOpen] = useState(false); // Thumbnail state const [thumbnailUrl, setThumbnailUrl] = useState(null); @@ -278,6 +280,25 @@ export function SavedRecipeView() {
{id && } {id && } +
+ {/* Cook mode overlay */} + {cookModeOpen && ( + setCookModeOpen(false)} /> + )} + {/* Delete confirmation modal */} {showDeleteConfirm && (