-
Notifications
You must be signed in to change notification settings - Fork 60
Recipe Library - Assignment Submission #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Nicolinabl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work!
I think you have done a very thorough job with your web site. I like that you had a bit of creative freedom with the design and layout. I especially like the use of emojis here and there for clarity and that you have a clear all button, good idea.
I see that you have put a lot of effort into your javascript file, i dont understand all of the code but i understand what it is supposed to do thanks to all of your structuring and explaining with comments.
| - **Live App:** https://recipelibrarya.netlify.app | ||
| - **Screenshot:** | ||
|
|
||
|  |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice idea to add a screen shot of the finished result in readme file
| <body> | ||
| <!-- Main Content --> | ||
| <main class="main-content"> | ||
| <div class="container"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: class name "container" could be more specific, so it is easier to see what it contains without having to read through all the code
| <h1 class="section-title">Recipe Library</h1> | ||
|
|
||
| <!-- Search Bar --> | ||
| <div class="search-container"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice and clear class name
| type="text" | ||
| id="search-input" | ||
| class="search-input" | ||
| placeholder="🔍 Search recipes by title or ingredients..." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that you also put the emoji in the placeholder and not only text
| <div class="dropdown-filters-row"> | ||
| <!-- Diet Filter --> | ||
| <div class="filter-group"> | ||
| <label class="filter-title" for="diet-filter">Dietary Preferences</label> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great that you have used labels, for accessibility
| } | ||
| }; | ||
|
|
||
| // Global recipe storage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea to clarify parts that are in global scope
| const clearFiltersBtn = document.getElementById('clear-filters-btn'); | ||
| const randomRecipeBtn = document.getElementById('random-recipe-btn'); | ||
| const favoritesBtn = document.getElementById('favorites-btn'); | ||
| const recipeContainer = document.getElementById('recipe-container'); | ||
| const messageDisplay = document.getElementById('message-display'); | ||
| const searchInput = document.getElementById('search-input'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice that you change the names in js from kebab-case to camelCase
| function extractDiets(spoonacularRecipe) { | ||
| const diets = []; | ||
| if (spoonacularRecipe.vegetarian) diets.push('vegetarian'); | ||
| if (spoonacularRecipe.vegan) diets.push('vegan'); | ||
| if (spoonacularRecipe.glutenFree) diets.push('gluten-free'); | ||
| if (spoonacularRecipe.dairyFree) diets.push('dairy-free'); | ||
| return diets; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice and clean function!
| } | ||
|
|
||
| /** | ||
| * Extract ingredient list from recipe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that you write a comment explaining what every function does
| * Clear all filter selections and reset the display | ||
| * Like clearAllFiltersQuiet but also shows all recipes again | ||
| */ | ||
| function clearAllFilters() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really like that you implemented a button to clear all chosen filters. I think that is good for user experience
JennieDalgren
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job with your project!
Your code is well structured and well documented. I really like that you put some effort into the Readme.
Great job explaining your code in the comments, some times I feel it might be slioghtly too much comments. You are using good naming convetions in your functions and variables so it might be explanatory enough some times to skip the comments, or keep them shorter. It is totally fine, also for your own sake to keep a lot of explanation in your code. However, when you're working on real production code I would suggest to trim it down a bit.
You have implemented all requreiements and stretch goals and you site is responsive. I like that you have added a little counter to tell how many favorites I have or how many vegetarian recipes there is for a specific filter. Also YAY for implementing pagination.
It seems like you had a lot of fun building this project and you really pushed yourself! Keep up the good work 🚀
| function mapSpoonacularToLocal(spoonacularRecipe) { | ||
| return { | ||
| id: spoonacularRecipe.id, | ||
| title: spoonacularRecipe.title, | ||
| image: spoonacularRecipe.image, | ||
| readyInMinutes: spoonacularRecipe.readyInMinutes || 30, | ||
| servings: spoonacularRecipe.servings || 4, | ||
| sourceUrl: spoonacularRecipe.sourceUrl || spoonacularRecipe.spoonacularSourceUrl, | ||
| diets: extractDiets(spoonacularRecipe), | ||
| cuisine: extractCuisine(spoonacularRecipe), | ||
| ingredients: extractIngredients(spoonacularRecipe), | ||
| pricePerServing: calculatePrice(spoonacularRecipe), | ||
| popularity: calculatePopularity(spoonacularRecipe) | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clever to create a function to organise your data as you want it
| function getMergedRecipes() { | ||
| const cachedRecipes = getCachedRecipes() || []; | ||
| const mappedBackup = backupData.recipes.map(mapSpoonacularToLocal); | ||
|
|
||
| // Merge both sources, removing duplicates by ID | ||
| const combinedRecipes = [...cachedRecipes]; | ||
| const existingIds = new Set(cachedRecipes.map(r => r.id)); | ||
|
|
||
| mappedBackup.forEach(recipe => { | ||
| if (!existingIds.has(recipe.id)) { | ||
| combinedRecipes.push(recipe); | ||
| } | ||
| }); | ||
|
|
||
| return combinedRecipes; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get what you're doing here, and nice job figuring that out. However, is it necessary?
| function toggleFavorite(recipeId) { | ||
| const index = favoriteRecipes.indexOf(recipeId); | ||
|
|
||
| if (index > -1) { | ||
| favoriteRecipes.splice(index, 1); | ||
| showMessage('❤️ Removed from favorites', 'success'); | ||
| } else { | ||
| favoriteRecipes.push(recipeId); | ||
| showMessage('💖 Added to favorites', 'success'); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!
| function setCachedRecipes(recipesToCache) { | ||
| try { | ||
| const cacheData = { | ||
| recipes: recipesToCache, | ||
| timestamp: Date.now(), | ||
| expires: Date.now() + (24 * 60 * 60 * 1000) | ||
| }; | ||
| localStorage.setItem('spoonacular_recipes', JSON.stringify(cacheData)); | ||
| console.log(`✅ Cached ${recipesToCache.length} recipes`); | ||
| } catch (error) { | ||
| console.warn('⚠️ Failed to cache recipes:', error); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great job with implementing local storage and also set a time limit for the cache
| function displayPage(recipes, pageNum = 1) { | ||
| if (recipes.length === 0) { | ||
| displayEmptyState(); | ||
| return; | ||
| } | ||
|
|
||
| const totalPages = Math.ceil(recipes.length / recipesPerPage); | ||
| currentPage = Math.max(1, Math.min(pageNum, totalPages)); | ||
|
|
||
| const startIndex = (currentPage - 1) * recipesPerPage; | ||
| const endIndex = startIndex + recipesPerPage; | ||
| const pageRecipes = recipes.slice(startIndex, endIndex); | ||
|
|
||
| const recipeCards = pageRecipes.map(r => createRecipeCardHTML(r)).join(''); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay! First project I review that implemented pagination. Well done ⭐
Recipe Library - Complete
https://recipelibrarya.netlify.app

✅ Requirements Met
✅ Stretch Goals Completed
Ready for review! 🎯