-
Notifications
You must be signed in to change notification settings - Fork 60
Code review project-recipe-library #45
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?
Changes from all commits
87f10d3
d715dcb
5179a00
c80dfa7
72bcf38
a1bd4bc
42bb0bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,3 @@ | ||
| # js-project-recipe-library | ||
|
|
||
| https://marina-recipelibrary.netlify.app/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
|
|
||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <link rel="stylesheet" href="style.css"> | ||
| <title>Project Recipe Library</title> | ||
| </head> | ||
|
|
||
| <body> | ||
| <div class="outer-container"> | ||
| <header> | ||
| <h1>Recipe Library</h1> | ||
| </header> | ||
|
|
||
| <section class="upper-box"> | ||
| <div class="filter"> | ||
| <p>Filter on kitchen</p> | ||
| <button class="btn-kitchen">All</button> | ||
| <button class="btn-kitchen">Asian</button> | ||
| <button class="btn-kitchen">European</button> | ||
| <button class="btn-kitchen">Mexican</button> | ||
| <button class="btn-kitchen">American</button> | ||
| <button class="btn-kitchen">Southern</button> | ||
| <button class="btn-kitchen">Mediterranean</button> | ||
| </div> | ||
|
|
||
| <div class="sorting"> | ||
| <p>Sort on time</p> | ||
| <button class="btn-sorting">Descending</button> | ||
| <button class="btn-sorting">Ascending</button> | ||
| </div> | ||
| <div class="random"> | ||
| <p>Random</p> | ||
| <button class="btn-random">🎲 Surprise Me!</button> | ||
| </div> | ||
| </section> | ||
|
|
||
| <main class="container" id="container"></main> | ||
| </div> | ||
| <script src="script.js"></script> | ||
|
|
||
| </body> | ||
|
|
||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| //=======GLOBAL VARIABLES=======// | ||
|
|
||
| const apiKey = "f7aa1eb0bfab45cdae5e9f2f21292092" | ||
| const container = document.getElementById("container") | ||
| const buttons = document.querySelectorAll(".btn-kitchen") | ||
| const sortButtons = document.querySelectorAll(".btn-sorting") | ||
| const randomButton = document.querySelector(".btn-random") | ||
|
|
||
| let selectedCuisine = "All" | ||
| let currentRecipes = [] | ||
|
|
||
| //======= FUNCTIONS for UI =======// | ||
|
|
||
| //Buttons, changes color when active// | ||
| function makesButtonInteractive(groupClass) { | ||
| const buttons = document.querySelectorAll(groupClass) | ||
|
|
||
| buttons.forEach((btn) => { | ||
| btn.addEventListener("click", () => { | ||
| buttons.forEach(b => b.classList.remove("active")) | ||
| btn.classList.add("active") | ||
| }) | ||
| }) | ||
| } | ||
|
|
||
| //Shows recipes in container// | ||
| const showRecipe = (recipeArray) => { | ||
| container.innerHTML = "" //Resetting the container before filling it// | ||
|
|
||
| recipeArray.forEach(recipe => { | ||
| const cuisineText = recipe.cuisines.length ? recipe.cuisines.join(", ") : "N/A" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. smart! |
||
|
|
||
| const timeText = recipe.readyInMinutes ? `${recipe.readyInMinutes} min` : "N/A" | ||
|
|
||
| container.innerHTML += ` | ||
| <div class="card"> | ||
| <img src="${recipe.image}" alt="${recipe.title}"> | ||
| <p><strong>${recipe.title}</strong></p> | ||
| <hr class="divider"> | ||
|
|
||
| <div class="info-row"> | ||
| <span><strong>Cuisine:</strong> ${cuisineText}</span> | ||
| <span><strong>Time: </strong> ${timeText}</span> | ||
| </div> | ||
|
|
||
| <hr class="divider"> | ||
| <p><strong>Ingredients:</strong></p> | ||
| <ul> | ||
| ${recipe.extendedIngredients | ||
| ?.slice(0, 5) | ||
| .map(i => `<li>${i.original}</li>`) | ||
| .join("") || "No ingredients"} | ||
| </ul> | ||
| </div> | ||
| `; | ||
| }); | ||
| }; | ||
|
|
||
| //======= EVENTLISTENERS =======// | ||
|
|
||
| //Filterbuttons// | ||
| buttons.forEach(button => { | ||
| button.addEventListener("click", () => { | ||
| selectedCuisine = button.textContent.trim() //trim removes invisible spaces at the beginning or end of a text | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good safety to trim |
||
| fetchRecipes() | ||
| }) | ||
| }) | ||
|
|
||
| //Sortingbuttons// | ||
| sortButtons.forEach(button => { | ||
| button.addEventListener("click", () => { | ||
| const chosenSort = button.textContent.trim() //Ascending/descending// | ||
| if (!currentRecipes.length) return | ||
|
|
||
| const sortedRecipes = [...currentRecipes] //coping the latest list from the API | ||
|
|
||
| sortedRecipes.sort((a, b) => { | ||
| const timeA = a.readyInMinutes || 0 | ||
| const timeB = b.readyInMinutes || 0 | ||
| return chosenSort === "Ascending" ? timeA - timeB : timeB - timeA | ||
| }) | ||
|
|
||
| showRecipe(sortedRecipes) | ||
| }) | ||
| }) | ||
|
|
||
| //Randombutton// | ||
| randomButton.addEventListener("click", () => { | ||
| if (!currentRecipes.length) { | ||
| container.innerHTML = | ||
| "<p> No recipes to show for this cuisine right now...</p>" | ||
| return | ||
| } | ||
|
|
||
| const randomIndex = Math.floor(Math.random() * currentRecipes.length) | ||
| const randomRecipe = currentRecipes[randomIndex] | ||
|
|
||
| showRecipe([randomRecipe]) | ||
| }) | ||
|
|
||
| //======= FETCH from API =======// | ||
|
|
||
| async function fetchRecipes() { | ||
| container.innerHTML = "<p>⏳ Loading recipes...</p>" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. loading state! ⭐ |
||
|
|
||
| try { | ||
| let url = `https://api.spoonacular.com/recipes/random?number=30&apiKey=${apiKey}&addRecipeInformation=true&addRecipeInstructions=true` | ||
|
|
||
| if (selectedCuisine !== "All") { | ||
| url += `&cuisine=${selectedCuisine}` //Use cuisines here instead of tags, to get sorted by cuisines// | ||
| } | ||
|
|
||
| const response = await fetch(url) | ||
| if (!response.ok) throw new Error("API-limit reached or network error") | ||
|
|
||
| const data = await response.json() | ||
|
|
||
| currentRecipes = data.recipes | ||
| .filter(recipe => recipe.cuisines && recipe.cuisines.length > 0) //Takes away recipes without cuisine | ||
| .filter(recipe => selectedCuisine === "All" || recipe.cuisines.includes(selectedCuisine)) //Filter on chosen cuisine | ||
|
|
||
| if (currentRecipes.length === 0) { | ||
| container.innerHTML = `<p>No recipes were found for this "${selectedCuisine}" 😕</p>` | ||
| } else { | ||
| showRecipe(currentRecipes) | ||
| } | ||
|
|
||
| } catch (err) { | ||
| container.innerHTML = `<p>⚠️ ${err.message}</p>` | ||
| } | ||
| } | ||
|
|
||
| //======= SETUP =======// | ||
|
|
||
| makesButtonInteractive(".btn-kitchen") //The function runs on both groups// | ||
| makesButtonInteractive(".btn-sorting") | ||
| fetchRecipes() //fetches all recipes when page loads | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| *, | ||
| /*html important to avoid side-scroll*/ | ||
| body, | ||
| html { | ||
| margin: 0; | ||
| padding: 0; | ||
| font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | ||
| box-sizing: border-box; | ||
| overflow-x: hidden; | ||
| } | ||
|
|
||
| .outer-container { | ||
| display: grid; | ||
| } | ||
|
|
||
| h1 { | ||
| width: 350px; | ||
| height: 70px; | ||
| margin: 50px 0 0 10px; | ||
| color: #0018A4; | ||
| font-size: 45px; | ||
| font-weight: bold; | ||
| line-height: 100%; | ||
| } | ||
|
|
||
|
|
||
| .upper-box { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 20px; | ||
| margin: 10px; | ||
| padding: 10px; | ||
| font-size: 20px; | ||
| font-weight: bold; | ||
| } | ||
|
|
||
| .btn-kitchen { | ||
| padding: 8px 16px 8px 16px; | ||
| margin-top: 10px; | ||
| background-color: #CCFFE2; | ||
| border-radius: 50px; | ||
| font-size: 16px; | ||
| border: none; | ||
| color: #0018A4; | ||
| } | ||
|
|
||
| .btn-kitchen.active { | ||
| background-color: #0018A4; | ||
| color: white; | ||
| } | ||
|
|
||
| .btn-kitchen:hover { | ||
| border: 2px solid #0018A4; | ||
| } | ||
|
|
||
| .btn-sorting { | ||
| padding: 8px 16px 8px 16px; | ||
| margin-top: 10px; | ||
| background-color: #FFECEA; | ||
| border-radius: 50px; | ||
| font-size: 16px; | ||
| border: none; | ||
| color: #0018A4; | ||
| } | ||
|
|
||
| .btn-sorting.active { | ||
| background-color: #FF6589; | ||
| color: white; | ||
| } | ||
|
|
||
| .btn-sorting:hover { | ||
| border: 2px solid #2d46d5; | ||
| } | ||
|
Comment on lines
+71
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid the "hopping" of the buttons when you hover over them you could add the same border on all the buttons but transparent color:
So the border is there all the time but only visible on hover. I hope that makes sense? |
||
|
|
||
| .btn-random { | ||
| width: 150px; | ||
| padding: 8px 16px 8px 16px; | ||
| margin-top: 10px; | ||
| background-color: rgb(212, 232, 240); | ||
| border-radius: 50px; | ||
| font-size: 14px; | ||
| border: none; | ||
| color: #0018A4; | ||
| } | ||
|
|
||
| .btn-random.active { | ||
| background-color: #0018A4; | ||
| color: white; | ||
| } | ||
|
|
||
| .btn-random:hover { | ||
| border: 2px solid #2d46d5; | ||
| } | ||
|
Comment on lines
+91
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you want to refactor the code and keep it more DRY you could stack similar styling together. |
||
|
|
||
| .container { | ||
| display: grid; | ||
| grid-template-columns: repeat(auto-fit, minmax(250px, max-content)); | ||
| width: 100%; | ||
| max-width: 320px; | ||
| justify-content: center; | ||
| gap: 20px; | ||
| padding: 10px; | ||
| margin: 0 auto; | ||
| } | ||
|
|
||
| .card { | ||
| font-size: 22px; | ||
| display: flex; | ||
| max-width: 300px; | ||
| flex-direction: column; | ||
| border: 1px solid #e9e9e9; | ||
| border-radius: 16px; | ||
| padding: 16px 16px 24px 16px; | ||
| } | ||
|
|
||
| .card:hover { | ||
| transform: scale(1.03); | ||
| box-shadow: 0px 0px 30px 0px #0018A433; | ||
| border: 2px solid #0018A4; | ||
| } | ||
|
|
||
| .card img { | ||
| object-fit: cover; | ||
| width: 100%; | ||
| height: 200px; | ||
| border-radius: 12px; | ||
| margin-bottom: 15px; | ||
| } | ||
|
|
||
| .card ul { | ||
| list-style-type: none; | ||
| font-size: 16px; | ||
| margin-top: 10px; | ||
| } | ||
|
|
||
| p { | ||
| font-size: 18px; | ||
| } | ||
|
|
||
| .info-row { | ||
| font-size: 16px; | ||
| } | ||
|
|
||
| .info-row span { | ||
| display: block; | ||
| padding: 5px; | ||
| } | ||
|
|
||
| ul li { | ||
| padding: 5px; | ||
| } | ||
|
|
||
| .divider { | ||
| width: 95%; | ||
| height: 0; | ||
| border: 1px solid #e9e9e9; | ||
| margin: 10px 0 10px 0; | ||
| } | ||
|
|
||
| /* //==== TABLET ====// */ | ||
| @media (min-width:600px) { | ||
| h1 { | ||
| font-size: 50px; | ||
| margin: 50px 0 0 20px; | ||
| } | ||
|
|
||
| .container { | ||
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | ||
| justify-content: center; | ||
| width: 100%; | ||
| max-width: 900px; | ||
| margin: 0 auto; | ||
| gap: 50px; | ||
| } | ||
| } | ||
|
|
||
| /* //==== DESKTOP ====// */ | ||
| @media (min-width: 1024px) { | ||
| .outer-container { | ||
| width: 100%; | ||
| padding-left: 40px; | ||
| box-sizing: border-box; | ||
| } | ||
|
|
||
| h1, | ||
| .upper-box { | ||
| margin-left: 0; | ||
| text-align: left; | ||
| } | ||
|
|
||
| .container { | ||
| grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | ||
| justify-content: start; | ||
| margin-left: 0; | ||
| gap: 30px; | ||
| max-width: 1600px; | ||
| } | ||
| } | ||
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.
Either stick to functions declared like:
function myFunctionName () {}or
const myFunctionName = () => {}don't mix