mirror of
https://github.com/Nezumi-2711/practice-js.git
synced 2026-09-22 13:38:49 +00:00
Finish Project
This commit is contained in:
+7
-5
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
+29
-3
@@ -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
@@ -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})`);
|
||||||
|
|||||||
+56
-18
@@ -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,22 +13,25 @@ export const state = {
|
|||||||
bookmarks: [],
|
bookmarks: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createRecipeObject = function (data) {
|
||||||
|
const { recipe } = data.data;
|
||||||
|
return {
|
||||||
|
id: recipe.id,
|
||||||
|
title: recipe.title,
|
||||||
|
publisher: recipe.publisher,
|
||||||
|
sourceUrl: recipe.source_url,
|
||||||
|
image: recipe.image_url,
|
||||||
|
servings: recipe.servings,
|
||||||
|
cookingTime: recipe.cooking_time,
|
||||||
|
ingredients: recipe.ingredients,
|
||||||
|
...(recipe.key && { key: recipe.key }),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const loadRecipe = async function (id) {
|
export const loadRecipe = async function (id) {
|
||||||
try {
|
try {
|
||||||
const data = await getJSON(`${API_URL}/${id}`);
|
const data = await AJAX(`${API_URL}/${id}?key=${API_KEY}`);
|
||||||
|
state.recipe = createRecipeObject(data);
|
||||||
const { recipe } = data.data;
|
|
||||||
|
|
||||||
state.recipe = {
|
|
||||||
id: recipe.id,
|
|
||||||
title: recipe.title,
|
|
||||||
publisher: recipe.publisher,
|
|
||||||
sourceUrl: recipe.source_url,
|
|
||||||
image: recipe.image_url,
|
|
||||||
servings: recipe.servings,
|
|
||||||
cookingTime: recipe.cooking_time,
|
|
||||||
ingredients: recipe.ingredients,
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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="">
|
||||||
|
|||||||
Reference in New Issue
Block a user