diff --git a/index.html b/index.html new file mode 100644 index 000000000..0dc4cd940 --- /dev/null +++ b/index.html @@ -0,0 +1,61 @@ + + + + + + Recipe Library + + + +
+

Recipe Library

+ +
+
+

Filter by cuisine

+
+ + + + +
+
+ +
+

Sort by time

+
+ + +
+
+
+ +
+
+ + + diff --git a/script.js b/script.js new file mode 100644 index 000000000..74744a453 --- /dev/null +++ b/script.js @@ -0,0 +1,135 @@ +// --- State --- +let cuisineQueryParam = ""; // e.g., "&cuisine=italian" +let sortQueryParam = "&sort=time&sortDirection=desc"; // default by time, descending +let activeFilterBtn = null; +let activeSortBtn = null; + +const API_KEY = "3f2445631f2d458b92fe40f9832bcc51"; + +// --- Marks the clicked button as active and removes the active class from the previously clicked one. --- +function markActive(newBtn, prevBtnRefName) { + if (newBtn === null) return; + if (window[prevBtnRefName]) { + window[prevBtnRefName].classList.remove("active"); + } + newBtn.classList.add("active"); + window[prevBtnRefName] = newBtn; +} + +//Fetches updated recipes and sends them to the UI rendering function (showCase()). +function updateAndRender() { + showCase(fetchRecipes()); +} + +// --- UI Actions Handles what happens when the user clicks a filter button (like “Italian” or “Mexican”).--- +function setActiveFilter(filter) { + // Set query param + if (filter === "all") { + cuisineQueryParam = ""; + } else { + cuisineQueryParam = "&cuisine=" + encodeURIComponent(filter); + } + // Mark active button + const btn = document.getElementById(`filter__${filter}`); + markActive(btn, "activeFilterBtn"); + + // Refresh results + updateAndRender(); +} + +function setActiveSort(direction) { + // Validate: asc | desc + const dir = direction === "asc" ? "asc" : "desc"; + sortQueryParam = `&sort=time&sortDirection=${dir}`; + + const btn = document.getElementById(`sort__${dir}`); + markActive(btn, "activeSortBtn"); + + // Refresh results + updateAndRender(); +} + +// --- Data Fetch recipe from spoonacular API, based on filter and sort settings --- +async function fetchRecipes() { + const URL = `https://api.spoonacular.com/recipes/complexSearch?fillIngredients=true&addRecipeInformation=true&number=12&apiKey=${API_KEY}${cuisineQueryParam}${sortQueryParam}`; + + try { + const response = await fetch(URL); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + const data = await response.json(); + // data.results is an array of recipe objects + return data.results || []; + } catch (err) { + console.error(err); + // Graceful fallback + return []; + } +} + +// --- Format time, and minustes --- +function formatTime(totalMinutes) { + const h = Math.floor(totalMinutes / 60); + const m = totalMinutes % 60; + if (h && m) return `${h}h ${m}m`; + if (h) return `${h}h`; + return `${m}m`; +} + +// Display recepie card in HTML Grid/ CSS style on it +async function showCase(recipesPromise) { + const recipeGrid = document.getElementById("recipe__grid"); + recipeGrid.innerHTML = `
Loading…
`; + + const recipes = await recipesPromise; + + if (!recipes.length) { + recipeGrid.innerHTML = `

No recipes found. Try a different filter.

`; + return; + } + + recipeGrid.innerHTML = ""; + + recipes.forEach((recipe) => { + const recipeCard = document.createElement("div"); + recipeCard.className = "recipe__card"; + + const cuisines = + Array.isArray(recipe.cuisines) && recipe.cuisines.length + ? recipe.cuisines.join(", ") + : "—"; + + const ingredients = Array.isArray(recipe.extendedIngredients) + ? recipe.extendedIngredients.map((ing) => ing.original) + : []; + + const ingredientList = document.createElement("ul"); + ingredientList.className = "recipe__ingredients"; + ingredients.forEach((text) => { + const li = document.createElement("li"); + li.textContent = text; + ingredientList.appendChild(li); + }); + // html structor + recipeCard.innerHTML = ` + ${recipe.title} +

${recipe.title}

+

Cuisine: ${cuisines}

+

Time: ${formatTime( + recipe.readyInMinutes || 0 + )}

+

Ingredients:

+ `; + + recipeCard.appendChild(ingredientList); + recipeGrid.appendChild(recipeCard); + }); +} +// initialization (auto-run- on load) +// --- Init (after DOM is ready because script is at the end of body) --- +(function init() { + // Set defaults visually & load first page + setActiveFilter("all"); + setActiveSort("desc"); +})(); diff --git a/styles.css b/styles.css new file mode 100644 index 000000000..3cb5dbb2b --- /dev/null +++ b/styles.css @@ -0,0 +1,105 @@ +* { + margin: 0; +} + +h1 { + color: blue; + font-size: 3.5rem; + margin-top: 50px; + margin-bottom: 50px; +} + +h2 { + margin: 25px 0 25px; +} + +#controls { + display: flex; + justify-content: space-between; + max-width: 900px; + flex-flow: row wrap; +} + +.controls__container { + display: flex; + justify-content: start; + gap: 30px; + flex-wrap: wrap; +} + +.controls__container > button { + color: #0018a4; + border: none; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + cursor: pointer; + border-radius: 30px; +} +.controls__container.filter > button { + background-color: #ccffe2; +} +.controls__container.filter > button.active { + color: white; + background-color: #0018a4; +} +.controls__container.filter > button:hover { + border: 2px blue solid; +} + +.controls__container.sort > button { + background-color: #ffecea; +} +.controls__container.sort > button.active { + background-color: #ff6589; +} +.controls__container.sort > button:hover { + border: 2px blue solid; + background-color: #ff6589; +} + +#recipe__grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + margin-top: 2rem; + column-gap: 10px; + row-gap: 20px; +} + +.recipe__card { + display: flex; + flex-direction: column; + padding: 1rem; + /* border: 2px grey solid; */ + border-radius: 10px; + width: 300px; + gap: 10px; + height: fit-content; + box-shadow: 1px 2px 3px 2px #dadada; +} + +.recipe__card > img { + border-radius: 20px; + align-self: center; + max-width: 100%; + margin: 16px; +} + +.recipe__card > h3, +h4 { + border-bottom: 1px grey solid; + padding-bottom: inherit; +} + +.recipe__card > ul { + list-style: none; + padding-inline-start: 0px; +} + +.container { + /* padding: 20px; */ + width: 90dvw; + margin: 0 auto; +}