Finish Project

This commit is contained in:
2023-08-17 11:42:17 +07:00
parent 7c528a5934
commit 7a63996376
9 changed files with 135 additions and 30 deletions
+7 -5
View File
@@ -19,7 +19,9 @@
<body> <body>
<div class="container"> <div class="container">
<header class="header"> <header class="header">
<img src="src/img/logo.png" alt="Logo" class="header__logo" /> <a href="/"
><img src="src/img/logo.png" alt="Logo" class="header__logo"
/></a>
<form class="search"> <form class="search">
<input <input
type="text" type="text"
@@ -106,13 +108,13 @@
<div class="upload__column"> <div class="upload__column">
<h3 class="upload__heading">Recipe data</h3> <h3 class="upload__heading">Recipe data</h3>
<label>Title</label> <label>Title</label>
<input value="TEST" required name="title" type="text" /> <input value="TEST23" required name="title" type="text" />
<label>URL</label> <label>URL</label>
<input value="TEST" required name="sourceUrl" type="text" /> <input value="TEST23" required name="sourceUrl" type="text" />
<label>Image URL</label> <label>Image URL</label>
<input value="TEST" required name="image" type="text" /> <input value="TEST23" required name="image" type="text" />
<label>Publisher</label> <label>Publisher</label>
<input value="TEST" required name="publisher" type="text" /> <input value="TEST23" required name="publisher" type="text" />
<label>Prep time</label> <label>Prep time</label>
<input value="23" required name="cookingTime" type="number" /> <input value="23" required name="cookingTime" type="number" />
<label>Servings</label> <label>Servings</label>
+2
View File
@@ -1,3 +1,5 @@
export const API_URL = 'https://forkify-api.herokuapp.com/api/v2/recipes'; export const API_URL = 'https://forkify-api.herokuapp.com/api/v2/recipes';
export const TIMEOUT_SEC = 10; export const TIMEOUT_SEC = 10;
export const RES_PER_PAGE = 10; export const RES_PER_PAGE = 10;
export const API_KEY = 'aa3a2565-6eea-42a4-bf0a-cb0077fce6cc';
export const MODAL_CLOSE_SEC = 2.5;
+28 -2
View File
@@ -1,4 +1,5 @@
import * as model from './model.js'; import * as model from './model.js';
import { MODAL_CLOSE_SEC } from './config.js';
import recipeView from './views/recipeView.js'; import recipeView from './views/recipeView.js';
import searchView from './views/searchView.js'; import searchView from './views/searchView.js';
import resultsView from './views/resultsView.js'; import resultsView from './views/resultsView.js';
@@ -90,10 +91,35 @@ const controlBookmarks = function () {
bookmarksView.render(model.state.bookmarks); bookmarksView.render(model.state.bookmarks);
}; };
const controlAddRecipe = function (newRecipe) { const controlAddRecipe = async function (newRecipe) {
console.log(newRecipe); try {
// Show loading spinner
addRecipeView.renderSpinner();
// Upload the new recipe data // Upload the new recipe data
await model.uploadRecipe(newRecipe);
console.log(model.state.recipe);
// Render recipe
recipeView.render(model.state.recipe);
// Success message
addRecipeView.renderMessage();
// Render bookmark view
bookmarksView.render(model.state.bookmarks);
// Change ID in URL
window.history.pushState(null, '', `#${model.state.recipe.id}`);
// Close form window
setTimeout(function () {
addRecipeView.toogleWindow();
}, MODAL_CLOSE_SEC * 1000);
} catch (err) {
console.error('💥', err);
addRecipeView.renderError(err.message);
}
}; };
const init = function () { const init = function () {
+12 -2
View File
@@ -9,9 +9,19 @@ const timeout = function (s) {
}); });
}; };
export const getJSON = async function (url) { export const AJAX = async function (url, uploadData = undefined) {
try { try {
const res = await Promise.race([fetch(url), timeout(TIMEOUT_SEC)]); const fetchPro = uploadData
? fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(uploadData),
})
: fetch(url);
const res = await Promise.race([fetchPro, timeout(TIMEOUT_SEC)]);
const data = await res.json(); const data = await res.json();
if (!res.ok) throw new Error(`${data.message} (${res.status})`); if (!res.ok) throw new Error(`${data.message} (${res.status})`);
+48 -10
View File
@@ -1,6 +1,6 @@
import { async } from 'regenerator-runtime'; import { async } from 'regenerator-runtime';
import { API_URL, RES_PER_PAGE } from './config'; import { API_URL, RES_PER_PAGE, API_KEY } from './config';
import { getJSON } from './helpers'; import { AJAX } from './helpers';
export const state = { export const state = {
recipe: {}, recipe: {},
@@ -13,13 +13,9 @@ export const state = {
bookmarks: [], bookmarks: [],
}; };
export const loadRecipe = async function (id) { const createRecipeObject = function (data) {
try {
const data = await getJSON(`${API_URL}/${id}`);
const { recipe } = data.data; const { recipe } = data.data;
return {
state.recipe = {
id: recipe.id, id: recipe.id,
title: recipe.title, title: recipe.title,
publisher: recipe.publisher, publisher: recipe.publisher,
@@ -28,7 +24,14 @@ export const loadRecipe = async function (id) {
servings: recipe.servings, servings: recipe.servings,
cookingTime: recipe.cooking_time, cookingTime: recipe.cooking_time,
ingredients: recipe.ingredients, ingredients: recipe.ingredients,
...(recipe.key && { key: recipe.key }),
}; };
};
export const loadRecipe = async function (id) {
try {
const data = await AJAX(`${API_URL}/${id}?key=${API_KEY}`);
state.recipe = createRecipeObject(data);
if (state.bookmarks.some(bookmark => bookmark.id === id)) if (state.bookmarks.some(bookmark => bookmark.id === id))
state.recipe.bookmarked = true; state.recipe.bookmarked = true;
@@ -42,7 +45,7 @@ export const loadSearchResults = async function (query) {
try { try {
state.search.query = query; state.search.query = query;
const data = await getJSON(`${API_URL}?search=${query}`); const data = await AJAX(`${API_URL}?search=${query}&key=${API_KEY}`);
state.search.results = data.data.recipes.map(rec => { state.search.results = data.data.recipes.map(rec => {
return { return {
@@ -50,6 +53,7 @@ export const loadSearchResults = async function (query) {
title: rec.title, title: rec.title,
publisher: rec.publisher, publisher: rec.publisher,
image: rec.image_url, image: rec.image_url,
...(rec.key && { key: rec.key }),
}; };
}); });
state.search.page = 1; state.search.page = 1;
@@ -107,4 +111,38 @@ const init = function () {
}; };
init(); init();
console.log(state.bookmarks);
export const uploadRecipe = async function (newRecipe) {
try {
const ingredients = Object.entries(newRecipe)
.filter(entry => entry[0].startsWith('ingredient') && entry[1] !== '')
.map(ing => {
const ingArr = ing[1].split(',').map(el => el.trim());
if (ingArr.length !== 3)
throw new Error(
'Wrong ingredient format! Please use the correct format!'
);
const [quantity, unit, description] = ingArr;
return { quantity: quantity ? +quantity : null, unit, description };
});
const recipe = {
title: newRecipe.title,
source_url: newRecipe.sourceUrl,
image_url: newRecipe.image,
publisher: newRecipe.publisher,
cooking_time: +newRecipe.cookingTime,
servings: +newRecipe.servings,
ingredients,
};
console.log(recipe);
const data = await AJAX(`${API_URL}?key=${API_KEY}`, recipe);
state.recipe = createRecipeObject(data);
addBookmark(state.recipe);
} catch (err) {
throw err;
}
};
+16
View File
@@ -75,4 +75,20 @@ export default class View {
this._clear(); this._clear();
this._parentElement.insertAdjacentHTML('afterbegin', markup); this._parentElement.insertAdjacentHTML('afterbegin', markup);
} }
renderMessage(message = this._message) {
const markup = `
<div class="message">
<div>
<svg>
<use href="${icons}#icon-smile"></use>
</svg>
</div>
<p>${message}</p>
</div>
`;
this._clear();
this._parentElement.insertAdjacentHTML('afterbegin', markup);
}
} }
+1
View File
@@ -3,6 +3,7 @@ import icons from '../../img/icons.svg';
class AddRecipeView extends View { class AddRecipeView extends View {
_parentElement = document.querySelector('.upload'); _parentElement = document.querySelector('.upload');
_message = 'Recipe was successfully uploaded.';
_window = document.querySelector('.add-recipe-window'); _window = document.querySelector('.add-recipe-window');
_overlay = document.querySelector('.overlay'); _overlay = document.querySelector('.overlay');
+8 -1
View File
@@ -15,9 +15,16 @@ class PreviewView extends View {
<figure class="preview__fig"> <figure class="preview__fig">
<img src="${this._data.image}" alt="${this._data.title}" /> <img src="${this._data.image}" alt="${this._data.title}" />
</figure> </figure>
<div class="preview__this._data"> <div class="preview__data">
<h4 class="preview__title">${this._data.title}</h4> <h4 class="preview__title">${this._data.title}</h4>
<p class="preview__publisher">${this._data.publisher}</p> <p class="preview__publisher">${this._data.publisher}</p>
<div class="preview__user-generated ${
this._data.key ? '' : 'hidden'
}">
<svg>
<use href="${icons}#icon-user"></use>
</svg>
</div>
</div> </div>
</a> </a>
</li> </li>
+4 -1
View File
@@ -76,7 +76,10 @@ class RecipeView extends View {
</div> </div>
</div> </div>
<div class="recipe__user-generated"> <div class="recipe__user-generated ${this._data.key ? '' : 'hidden'}">
<svg>
<use href="${icons}#icon-user"></use>
</svg>
</div> </div>
<button class="btn--round btn--bookmark"> <button class="btn--round btn--bookmark">
<svg class=""> <svg class="">