diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..fba504746 Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index 58f1a8a66..a5b7fa82c 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # js-project-recipe-library + +https://mikaelas-js-project-recipe-library.netlify.app/ \ No newline at end of file diff --git a/images/focaccia.jpg b/images/focaccia.jpg new file mode 100644 index 000000000..a1ea3cb98 Binary files /dev/null and b/images/focaccia.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..17e910f2d --- /dev/null +++ b/index.html @@ -0,0 +1,91 @@ + + + + + + + + Recipe Library + + + + +

Recipe Library

+ +
+
+
+

Filter on dish

+ +
+ + + + + + + +
+ +
+ +
+

Filter on kitchen

+ +
+ + + + + + + + +
+ +
+

Gluten free?

+ +
+ +
+ +
+
+
+ + +
+ +
+

Sort on time

+ +
+ + +
+ +
+ +
+

Can't decide?

+ +
+ +
+ +
+ +
+
+ +
+ +
Loading...
+ +
+ + + + + \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 000000000..ced514618 --- /dev/null +++ b/index.js @@ -0,0 +1,257 @@ +const API_KEY = '0c53a9868a9e4217a3adc272f97c6cf8'; +const URL = `https://api.spoonacular.com/recipes/complexSearch?number=60&fillIngredients=true&type=starter,main%20course,dessert,side%20dish,lunch,dinner&cuisine=African,Asian,American,Cajun,European,Latin%20American,Middle%20Eastern&addRecipeInformation=true&apiKey=${API_KEY}`; + +const card = document.getElementById('card') +const loadingCard = document.getElementById('loadingCard') +const filterDishButtons = document.querySelectorAll('.filter-dish button'); +const filterCuisineButtons = document.querySelectorAll('.filter-cuisine button'); +const filterGlutenFreeButton = document.querySelector('.filter-gluten-free button'); +const sortButtons = document.querySelectorAll('.sort-button button'); +const randomButton =document.querySelector('.random-button button'); + +const displayRecipes = recipeCard => { + card.innerHTML = '' + + if (!recipeCard || recipeCard.length === 0) { + card.innerHTML = ` +
+

Inga recept hittades

+

Prova att filtrera på något annat.

+
`; + return;} + + recipeCard.forEach(recipe => { + card.innerHTML += ` + +
+ ${recipe.title} + +

${recipe.title}

+ +
+ +

Dish type: ${recipe.dishTypes.join(', ') || 'Unknown Dish Type'}

+

Cuisine: ${recipe.cuisines.join(', ') || 'Unknown Cuisine'}

+

Time: ${recipe.readyInMinutes || 'N/A'} minutes

+ +
+ +

Ingredients

+ +

${recipe.extendedIngredients + ? recipe.extendedIngredients.map(ingredient => ingredient.original).join(',
') + : 'No ingredients listed'} +

+
+
+ ` + }) +} + +const localRecipes = [ + { + title: 'Focaccia Bread', + image: 'images/focaccia.jpg', + dishTypes: ['bread', 'side dish'], + cuisines: ['Italian', 'European'], + readyInMinutes: 45, + extendedIngredients: [ + { original: 'flour' }, + { original: 'olive oil' }, + { original: 'yeast' }, + { original: 'salt' } + ] + }, + { + title: 'Veggie Tacos', + image: 'images/tacos.jpg', + dishTypes: ['taco', 'main course'], + cuisines: ['Mexican', 'Latin American'], + readyInMinutes: 25, + extendedIngredients: [ + { original: 'tortillas' }, + { original: 'beans' }, + { original: 'avocado' }, + { original: 'lime' } + ] + }, + { + title: 'Ramen Soup', + image: 'images/ramen.jpg', + dishTypes: ['soup', 'main course'], + cuisines: ['Asian'], + readyInMinutes: 30, + extendedIngredients: [ + { original: 'noodles' }, + { original: 'egg' }, + { original: 'broth' }, + { original: 'spring onions' } + ] + }, + { + title: 'Pad Thai', + image: 'images/padthai.jpg', + dishTypes: ['main course'], + cuisines: ['Asian'], + readyInMinutes: 30, + extendedIngredients: [ + { original: 'rice noodles' }, + { original: 'shrimp' }, + { original: 'bean sprouts' }, + { original: 'peanuts' }, + { original: 'lime' } + ] + }, + { + title: 'Caesar Salad', + image: 'images/caesarsalad.jpg', + dishTypes: ['salad', 'side dish'], + cuisines: ['American'], + readyInMinutes: 15, + extendedIngredients: [ + { original: 'romaine lettuce' }, + { original: 'croutons' }, + { original: 'parmesan' }, + { original: 'caesar dressing' } + ] + }, + { + title: 'Chicken Curry', + image: 'images/chickencurry.jpg', + dishTypes: ['main course'], + cuisines: ['Asian'], + readyInMinutes: 40, + extendedIngredients: [ + { original: 'chicken' }, + { original: 'coconut milk' }, + { original: 'curry paste' }, + { original: 'onion' }, + { original: 'garlic' } + ] +} +]; + +let currentRecipes = [] +let currentSortOrder = null; + +const simplifyButtonText = text => text.trim().toLowerCase() || ''; + +const getActiveFilters = (buttons) => { + const active = [...buttons] + .filter(button => button.classList.contains('active')) + .map(button => simplifyButtonText(button.textContent)); + if (active.length === 0 || active.includes('all')) return null; + return new Set(active); +} + +const applyFilters = () => { + let filteredRecipes = currentRecipes; + + const dishTypes = getActiveFilters(filterDishButtons); + const cuisines = getActiveFilters(filterCuisineButtons); + const glutenFree = filterGlutenFreeButton.classList.contains('active'); + + if (dishTypes) { + filteredRecipes = filteredRecipes.filter(recipe => recipe.dishTypes.some(type => dishTypes.has(simplifyButtonText(type)))); + } if (cuisines) { + filteredRecipes = filteredRecipes.filter(recipe => recipe.cuisines.some(cuisine => cuisines.has(simplifyButtonText(cuisine)))); + } if (glutenFree) { + filteredRecipes = filteredRecipes.filter(recipe => recipe.glutenFree); + } if (currentSortOrder === 'fast') { + filteredRecipes = filteredRecipes.sort((a, b) => a.readyInMinutes - b.readyInMinutes); + } else if (currentSortOrder === 'slow') { + filteredRecipes = filteredRecipes.sort((a, b) => b.readyInMinutes - a.readyInMinutes); + } + displayRecipes(filteredRecipes); +} + +const handleError = error => { + alert("The API request limit has been reached. But don't worry, you can still view some local recipes!"); + currentRecipes = localRecipes; + displayRecipes(localRecipes); +} + +fetch(URL) +.then(response => response.json()) +.then(data => { + currentRecipes = data.results; + displayRecipes(currentRecipes); +}) +.catch(handleError); + +filterDishButtons.forEach(button => { + button.addEventListener('click', () => { + const isAll = button.textContent.trim().toLowerCase() === 'all'; + + if (isAll) { + filterDishButtons.forEach(button => button.classList.remove('active')); + button.classList.add('active'); + applyFilters(); + } else { + const allButton = Array.from(filterDishButtons).find(button => button.textContent.trim().toLowerCase() === 'all'); + if (allButton) allButton.classList.remove('active'); + + button.classList.toggle('active'); + + const anyActive = Array.from(filterDishButtons).some(button => button.classList.contains('active')); + if (!anyActive && allButton) { + allButton.classList.add('active'); + } + applyFilters(); + } + }); +}); + +filterCuisineButtons.forEach(button => { + button.addEventListener('click', () => { + const isAll = button.textContent.trim().toLowerCase() === 'all'; + + if (isAll) { + filterCuisineButtons.forEach(button => button.classList.remove('active')); + button.classList.add('active'); + applyFilters(); + } else { + const allButton = Array.from(filterCuisineButtons).find(button => button.textContent.trim().toLowerCase() === 'all'); + if (allButton) allButton.classList.remove('active'); + + button.classList.toggle('active'); + + const anyActive = Array.from(filterCuisineButtons).some(button => button.classList.contains('active')); + if (!anyActive && allButton) { + allButton.classList.add('active'); + } + applyFilters(); + } + }); +}); + +filterGlutenFreeButton.addEventListener('click', () => { + filterGlutenFreeButton.classList.toggle('active'); + filterGlutenFreeButton.textContent = filterGlutenFreeButton.classList.contains('active') ? 'Yes' : 'No'; + applyFilters(); +}); + +sortButtons.forEach(button => { + button.addEventListener('click', () => { + sortButtons.forEach(button => button.classList.remove('active')); + button.classList.add('active'); + + const sortOrder = button.textContent.trim().toLowerCase(); + + currentSortOrder = sortOrder.includes('fastest') ? 'fast' : 'slow'; + + applyFilters(); + }); +}); + +randomButton.addEventListener('click', () => { + randomButton.classList.toggle('active'); + randomButton.textContent = randomButton.classList.contains('active') ? 'Back to All' : 'Random recipe'; + const randomRecipe = currentRecipes[Math.floor(Math.random() * currentRecipes.length)]; + + if (randomButton.classList.contains('active')) { + displayRecipes([randomRecipe]); + } else { + displayRecipes(currentRecipes); + } +}); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 000000000..5b390756b --- /dev/null +++ b/style.css @@ -0,0 +1,175 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + height: 100%; + width: 100%; + font-family: Arial, Helvetica, sans-serif; + color: #000000; + background-color: #FAFBFF; +} + +h1 { + color: #0018A4; + font-size: 64px; + font-style: bold; + /* height: 85px; */ + width: 320px; + line-height: 100%; + padding: 50px 20px 30px; +} + +.filter-menu { + display: flex; + flex-direction: column; + gap: 20px; + padding: 20px; +} + +.filter-box { + gap: 16px; +} + +h2 { + width: 189px; + /* height: 29px; */ + font-style: bold; + font-size: 22px; + text-align: left; + line-height: 100%; + padding: 0 0 10px 5px; +} + +button { + border-radius: 50px; + padding: 8px 16px; + margin: 0 0 10px 0; + border: none; +} + +button:hover { + border: solid 2px #0018A4; +} + +button::selection { + border: none; +} + +.filter-box button { + background-color: #CCFFE2A4; + color: #0018A4; +} + +.filter-box button.active { + background-color: #0018A4; + color: #fff; + border: none; +} + +.sort-box button { + background-color: #FFECEA; + color: #0018A4; +} + +.sort-box button.active { + background-color: #FF6589; + color: #fff; + border: none; +} + +.random-box button { + background-color: #FFECEA; + color: #0018A4; +} + +.random-box button.active { + background-color: #FF6589; + color: #fff; + border: none; +} + +a{ + text-decoration-line: none; + color: inherit; +} + +.card-grid { + display: grid; + gap: 16px; + justify-content: center; + width: 100%; + max-width: 100%; + grid-template-columns: repeat(1, 300px); +} + +.card { + color: #000000; + background-color: #fff; + border: solid 2px #E9E9E9; + border-radius: 16px; + width: 300px; + padding: 16px 16px 24px 16px; + display: flex; + flex-direction: column; + gap: 16px; + /* margin: 20px auto; */ +} + +hr { + border: none; + height: 1px; + background-color: #E9E9E9; +} + +.card:hover { + border: solid 2px #0018A4; + box-shadow: 0px 0px 30px 0px #0018A433; +} + +.ingredients-list { +display: flex; +flex-direction: column; +flex-wrap: wrap; +} + +img { + width: 268px; + height: 200px; + border-radius: 12px; + color: #899CCC; +} + +/*Tablet responsive design*/ +@media (min-width: 667px) { + + h1 { + font-size: 64px; + width: 495px; + } + + .filter-menu { + display: flex; + flex-direction: row; + gap: 88px; + } + + .card-grid { + grid-template-columns: repeat(2, 300px); + } +} + +@media (min-width: 940px) { + .card-grid { + grid-template-columns: repeat(3, 300px); + } +} + +/*Desktop-ish responsive design*/ +@media (min-width: 1260px) { + .card-grid { + grid-template-columns: repeat(4, 300px); + } +} \ No newline at end of file