diff --git a/TODO.md b/TODO.md index 7bd9d2c..cd3e19b 100644 --- a/TODO.md +++ b/TODO.md @@ -21,12 +21,11 @@ - [x] Set up React Router - [x] Create recipe list page - [x] Create recipe detail/edit page -- [ ] Implement cook mode UI +- [x] Implement cook mode UI ### Features - [ ] Tag management (create, assign, filter) - [ ] Text search (title + ingredients) -- [ ] Screen wake lock for cook mode - [ ] Basic error handling + loading states ### DevOps @@ -46,6 +45,15 @@ ## ✅ Completed Tasks ### 2026-03-24 +- **Cook mode UI implementation** + - Implemented full cooking interface with ingredient and step checklists + - Added progress tracking with visual progress bars + - Integrated Screen Wake Lock API to prevent screen sleep during cooking + - Created touch-friendly UI with large text and clear spacing + - Added completion celebration when all steps are done + - Included loading and error states with navigation back to recipe detail + - Verified TypeScript compilation and Vite build succeed + - **Recipe detail/edit page implementation** - Created useRecipe hook for fetching single recipe with loading/error states - Created RecipeForm component with full validation (title, ingredients, instructions required) diff --git a/frontend/src/pages/CookModePage.tsx b/frontend/src/pages/CookModePage.tsx index e32ea09..d0a1d02 100644 --- a/frontend/src/pages/CookModePage.tsx +++ b/frontend/src/pages/CookModePage.tsx @@ -1,27 +1,301 @@ -import { useParams } from 'react-router-dom'; +import { useState, useEffect } from 'react'; +import { useParams, Link } from 'react-router-dom'; +import { useRecipe } from '../hooks/useRecipe'; /** * CookModePage - Hands-free cooking interface with wake lock */ export function CookModePage() { const { id } = useParams<{ id: string }>(); + const recipeId = id ? parseInt(id, 10) : null; + const { recipe, loading, error } = useRecipe(recipeId); + + // Track checked ingredients and steps + const [checkedIngredients, setCheckedIngredients] = useState>(new Set()); + const [checkedSteps, setCheckedSteps] = useState>(new Set()); + + // Wake lock state + const [wakeLock, setWakeLock] = useState(null); + const [wakeLockSupported, setWakeLockSupported] = useState(false); + + // Check if Wake Lock API is supported + useEffect(() => { + setWakeLockSupported('wakeLock' in navigator); + }, []); + + // Request wake lock + const requestWakeLock = async () => { + if (!wakeLockSupported) return; + + try { + const lock = await navigator.wakeLock.request('screen'); + setWakeLock(lock); + + // Handle wake lock release + lock.addEventListener('release', () => { + setWakeLock(null); + }); + } catch (err) { + console.error('Failed to request wake lock:', err); + } + }; + + // Release wake lock + const releaseWakeLock = async () => { + if (wakeLock) { + await wakeLock.release(); + setWakeLock(null); + } + }; + + // Toggle wake lock + const toggleWakeLock = () => { + if (wakeLock) { + releaseWakeLock(); + } else { + requestWakeLock(); + } + }; + + // Release wake lock when leaving page + useEffect(() => { + return () => { + if (wakeLock) { + wakeLock.release(); + } + }; + }, [wakeLock]); + + // Toggle ingredient checkbox + const toggleIngredient = (index: number) => { + setCheckedIngredients(prev => { + const next = new Set(prev); + if (next.has(index)) { + next.delete(index); + } else { + next.add(index); + } + return next; + }); + }; + + // Toggle step checkbox + const toggleStep = (index: number) => { + setCheckedSteps(prev => { + const next = new Set(prev); + if (next.has(index)) { + next.delete(index); + } else { + next.add(index); + } + return next; + }); + }; + + // Loading state + if (loading) { + return ( +
+
+
+

Loading recipe...

+
+
+ ); + } + + // Error state + if (error || !recipe) { + return ( +
+

Error Loading Recipe

+

{error || 'Recipe not found'}

+ + Back to Recipes + +
+ ); + } + + // Calculate progress + const ingredientsTotal = recipe.ingredients.length; + const ingredientsChecked = checkedIngredients.size; + const stepsTotal = recipe.instructions.length; + const stepsChecked = checkedSteps.size; + const ingredientsProgress = ingredientsTotal > 0 ? (ingredientsChecked / ingredientsTotal) * 100 : 0; + const stepsProgress = stepsTotal > 0 ? (stepsChecked / stepsTotal) * 100 : 0; return ( -
-
-

- Cook Mode {id && `- Recipe #${id}`} -

-

- Step-by-step cooking interface -

+
+ {/* Header */} +
+
+
+

{recipe.title}

+ {recipe.description && ( +

{recipe.description}

+ )} +
+ + Exit Cook Mode + +
+ + {/* Recipe metadata */} +
+ {recipe.servings && ( +
+ Servings: + {recipe.servings} +
+ )} + {recipe.prep_time_minutes && ( +
+ Prep: + {recipe.prep_time_minutes} min +
+ )} + {recipe.cook_time_minutes && ( +
+ Cook: + {recipe.cook_time_minutes} min +
+ )} +
+ + {/* Wake lock toggle */} + {wakeLockSupported && ( +
+ +

+ {wakeLock + ? 'Your screen will stay on while cooking' + : 'Enable to prevent your screen from turning off'} +

+
+ )}
-
-

- Cook mode interface will be implemented later -

+ {/* Ingredients Section */} +
+
+

Ingredients

+
+ {ingredientsChecked} of {ingredientsTotal} +
+
+ + {/* Progress bar */} +
+
+
+ + {/* Ingredient checklist */} +
+ {recipe.ingredients.map((ingredient, index) => ( + + ))} +
+ + {/* Instructions Section */} +
+
+

Instructions

+
+ {stepsChecked} of {stepsTotal} +
+
+ + {/* Progress bar */} +
+
+
+ + {/* Instruction steps */} +
+ {recipe.instructions.map((instruction, index) => ( + + ))} +
+
+ + {/* Completion message */} + {ingredientsChecked === ingredientsTotal && stepsChecked === stepsTotal && ( +
+
🎉
+

All Done!

+

+ You've completed all steps. Enjoy your meal! +

+ + Back to Recipe + +
+ )}
); }