diff --git a/index.html b/index.html index 0c9bf696..8926ff9a 100644 --- a/index.html +++ b/index.html @@ -5,13 +5,14 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Recipe Library</title> <link rel="stylesheet" href="style.css" /> + <link + rel="stylesheet" + href="https://fonts.googleapis.com/icon?family=Material+Icons" + /> </head> <body> <div class="header-container"> <h1>Recipe Library</h1> - <section class="random-button-container"> - <button class="random-btn">Love at first bite? 😋</button> - </section> </div> <div id="loader" class="spinner"> @@ -22,7 +23,9 @@ <h1>Recipe Library</h1> <div class="filters-container"> <div class="filter-dropdown"> - <button class="dropdown-btn">Filters</button> + <button class="dropdown-btn"> + <span class="material-icons">filter_list</span> Filters + </button> <div class="dropdown-content"> <div class="filter-group"> <h3>Diet</h3> @@ -84,7 +87,9 @@ <h3>Cooking Time</h3> </div> <div class="sort-dropdown"> - <button class="dropdown-btn">Sort</button> + <button class="dropdown-btn"> + <span class="material-icons">sort</span> Sort + </button> <div class="dropdown-content"> <div class="sort-group"> <label @@ -110,9 +115,23 @@ <h3>Cooking Time</h3> </div> </div> </div> + + <section class="random-button-container"> + <button class="random-btn"> + <span class="material-icons">shuffle</span> + </button> + </section> </div> - <div class="recipes-grid"></div> + <div class="recipes-grid"> + <div class="quota-message"> + <h2>Daily Limit Reached</h2> + <p class="quota-description"> + It's amazing that you want more recipes! However, you've reached the + daily limit of recipe requests. Come back tomorrow ♥️! + </p> + </div> + </div> <script src="script.js"></script> </body> diff --git a/script.js b/script.js index 4f75acb2..20811ba8 100644 --- a/script.js +++ b/script.js @@ -1,8 +1,6 @@ document.addEventListener("DOMContentLoaded", () => { - const BASE_URL = "https://api.spoonacular.com/recipes/random"; - const API_KEY = "76c7e57bf79245a0a12a395c3fdb2f0b"; - const API_URL = `${BASE_URL}/?apiKey=${API_KEY}&number=30`; - + const API_URL = + "https://api.spoonacular.com/recipes/random?number=12&include-tags=vegetarian&apiKey=1f7a525474994d99b2f2a00a1f826e01"; const recipesGrid = document.querySelector(".recipes-grid"); const filterDropdown = document.querySelector(".filter-dropdown"); const sortDropdown = document.querySelector(".sort-dropdown"); @@ -90,6 +88,15 @@ document.addEventListener("DOMContentLoaded", () => { const fetchRecipes = async () => { try { const response = await fetch(API_URL); + + if (response.status === 402) { + console.warn( + "Quota limit reached! Calling showQuotaExceededMessage()." + ); + showQuotaExceededMessage(); + return []; + } + const data = await response.json(); const validRecipes = data.recipes.filter((recipe) => { return ( @@ -102,22 +109,40 @@ document.addEventListener("DOMContentLoaded", () => { }); return validRecipes; } catch (error) { - console.error("Error fetching recipes:", error); + console.warn("Error fetching recipes:", error); return []; } }; - const displayRecipes = (recipes, isRandomRecipe = false) => { + const displayRecipes = (recipes) => { + const closeDropdowns = () => { + document.querySelectorAll(".dropdown-content").forEach((content) => { + content.style.display = "none"; + }); + + document.querySelectorAll(".dropdown.content").forEach((btn) => { + btn.classList.remove("active"); + }); + }; + recipesGrid.innerHTML = ""; // recipes = [{}, {}, {}, {}] if (recipes.length === 0) { - console.log("No recipes found. Hiding button."); recipesGrid.classList.add("no-recipes-active"); randomButtonContainer.classList.add("hidden"); recipesGrid.innerHTML = `<div class="no-recipes"> <img src="computersaysno.gif" alt="No recipes match your filters."> + <p>No recipes match the filters. Sorry ♥️! </p> + <button class="reset-filters-btn">Reset Filters</button> </div> `; + + closeDropdowns(); + + document + .querySelector(".reset-filters-btn") + .addEventListener("click", resetFilters); + return; } recipesGrid.classList.remove("no-recipes-active"); @@ -148,17 +173,29 @@ document.addEventListener("DOMContentLoaded", () => { <img src="${recipe.image}" alt="${recipe.title}"> <div class="recipe-content"> <h3 class="recipe-title">${recipe.title}</h3> - <p class="recipe-meta">Cuisine: ${ - recipe.cuisines.join(", ") || "Not specified" - }</p> - <p class="recipe-meta">Time: ${recipe.readyInMinutes} min</p> + <hr> + <div> + <p class="recipe-meta"> + <span class="material-icons">restaurant</span> ${ + recipe.cuisines[0] || "-" + }</p> + <p class="recipe-meta"> + <span class="material-icons">schedule</span> ${ + recipe.readyInMinutes + } min + </p> + </div> + <hr> + <h4 class="recipe-subtitle">Ingredients</h4> <ul class="ingredient-list"> ${recipe.extendedIngredients .slice(0, 4) .map( (ingredient) => - `<li class="ingredient-list-item">${ingredient.name}</li>` + `<li class="ingredient-list-item">${capitalize( + ingredient.name + )}</li>` ) .join("")} </ul> @@ -196,6 +233,39 @@ document.addEventListener("DOMContentLoaded", () => { recipesGrid.appendChild(container); }; + const resetFilters = () => { + // Uncheck all checkboxes + document + .querySelectorAll('input[type="checkbox"], input[type="radio"]') + .forEach((input) => { + input.checked = false; + }); + + // Show all recipes again + displayRecipes(allRecipes); + }; + + const showQuotaExceededMessage = () => { + console.log("Showing quota exceeded message..."); + // Hide the loader in case it's still visible + const loader = document.getElementById("loader"); + if (loader) loader.style.display = "none"; + + const recipesGrid = document.querySelector(".recipes-grid"); + const quotaMessage = document.querySelector(".quota-message"); + + if (recipesGrid) { + recipesGrid.style.display = "block"; // Ensure it's visible + } + + if (quotaMessage) { + quotaMessage.style.display = "block"; // Make it visible + quotaMessage.style.opacity = "1"; // Ensure it's visible + } else { + console.error("Quota message element not found."); + } + }; + const filterRecipes = (recipes) => { const filters = { diet: Array.from( @@ -259,6 +329,9 @@ document.addEventListener("DOMContentLoaded", () => { return "over60"; }; + const capitalize = (s) => + s && String(s[0]).toUpperCase() + String(s).slice(1); + const updateRecipes = async () => { const loader = document.getElementById("loader"); loader.style.display = "block"; // Show the spinner @@ -267,6 +340,7 @@ document.addEventListener("DOMContentLoaded", () => { try { if (allRecipes.length === 0) { allRecipes = await fetchRecipes(); + if (allRecipes.length === 0) return; } const recipes = allRecipes; const filteredRecipes = filterRecipes(recipes); diff --git a/style.css b/style.css index b8854f6d..62eba218 100644 --- a/style.css +++ b/style.css @@ -5,6 +5,12 @@ font-family: Futura, "Trebuchet MS", Arial, sans-serif; } +html, +body { + width: 100%; + overflow-x: hidden; +} + body { background: #f5f5f5; padding: 2rem; @@ -27,7 +33,6 @@ h1 { margin-left: 1rem; display: flex; justify-content: center; - margin: 0.5em; } .random-btn { @@ -35,17 +40,18 @@ h1 { padding: 0.5em 1em; border-radius: 20px; cursor: pointer; - font-size: 1em; + font-size: 1rem; font-weight: 500; transition: all 0.2s; - background: #ffc700; - color: #ff4575; + background: #fff; + color: #0018a4; border: solid 2px transparent; + display: flex; } .random-btn:hover { - background: #ffc700; - color: #ff4575; + background: #0018a4; + color: #fff; border: #0018a4 solid 2px; } @@ -147,6 +153,9 @@ h1 { font-size: 1rem; font-weight: 500; transition: all 0.2s; + display: flex; + align-items: center; + gap: 8px; } .filter-dropdown .dropdown-btn { @@ -190,7 +199,7 @@ h1 { background-color: white; min-width: 200px; box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); - z-index: 1; + z-index: 1000; border-radius: 8px; padding: 1rem; } @@ -230,7 +239,7 @@ h1 { width: 100%; max-width: 300px; margin: 0 auto; - border: #e9e9e9 sold 2px; + border: #e9e9e9 solid 2px; } .recipe-card:hover { @@ -248,7 +257,15 @@ h1 { } .recipe-content { - padding: 1.5rem; + padding: 0 1.5rem 1.5rem; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.recipe-content hr { + border-color: #e9e9e9; + border-style: solid; } .recipe-title { @@ -257,6 +274,10 @@ h1 { color: #000000; } +.recipe-subtitle { + font-size: 1.15rem; +} + /* .divider { height: 2px; background: #ff6589; @@ -265,8 +286,15 @@ h1 { } */ .recipe-meta { - font-size: 0.8rem; + font-size: 1rem; color: #666; + line-height: 1.5em; +} + +.recipe-meta .material-icons { + font-size: 1.1rem; + position: relative; + top: 0.15rem; } .recipe-card.hidden { @@ -277,6 +305,40 @@ h1 { list-style: none; } +.quota-message { + display: none; + opacity: 0; + transition: opacity 0.5s ease-in-out; +} + +.show { + display: block; +} + +.quota-message.show { + display: block; + opacity: 1; +} + +.reset-filters-btn { + border: none; + padding: 0.5em 1em; + border-radius: 20px; + cursor: pointer; + font-size: 1em; + font-weight: 500; + transition: all 0.2s; + background: #ffc700; + color: #ff4575; + border: solid 2px transparent; +} + +.reset-filters-btn:hover { + background: #ffc700; + color: #ff4575; + border: #0018a4 solid 2px; +} + .recipes-grid.no-recipes-active { display: flex; flex-direction: column; @@ -289,10 +351,17 @@ h1 { .no-recipes { display: flex; - justify-content: center; + justify-content: space-between; align-items: center; height: 300px; /* or whatever height works in your layout */ width: 100%; + flex-direction: column; +} + +.quota-description { + padding: 1em 0 0; + font-size: 1em; + line-height: 1.6; } .no-recipes img { @@ -320,7 +389,7 @@ h1 { @media (max-width: 768px) { .filters-container { - flex-direction: column; + /* flex-direction: column; */ } .dropdown-content { diff --git a/todo.md b/todo.md index c6ab4348..ddadf9ff 100644 --- a/todo.md +++ b/todo.md @@ -1,25 +1,24 @@ -## priority +## To dos (priority) -Debug message you get from no matching filters[] +1. Show message when daily quota has been reached [X – need to double check] -displsy a message if 150 API calls have been made [] +2. Change cuisine filter to amounts of ingredients [] -Shoow recipe link when clicking recipe [] +3. Make sure you can back to all recipes when once no filter is met [X] -Make sure you do not accidentally scroll on mobile [] +4. Add an all recipes button [] -Create an all recipes button in the top of the filter [] +5. Add filtering symbol [X] -'Back to all recipes' button ta appear when random recipe is shown tha takes you back to the already fetched recipes[] +6. Add sorting symbol [X] + +## Stretch -Stretch: Allow users to search for specific recipe names or ingredients [] sorting on amount of ingredients [] -filtering on amount of ingredients [] - -Allow users to save/like recipes and store them in local storage. This includes adding a heart button to the recipe card and adding a "View favourites" button that only shows favourite recipes. [] +Show recipe link when clicking recipe [] // "https://api.spoonacular.com/recipes/random?number=12&include-tags=vegetarian,gluten-free,dairy-free,Mediterranean,Asian,Italian,Mexican&apiKey=76c7e57bf79245a0a12a395c3fdb2f0b";