mirror of
https://github.com/Nezumi-2711/practice-js.git
synced 2026-09-22 05:32:05 +00:00
Finish Project
This commit is contained in:
+7
-5
@@ -19,7 +19,9 @@
|
||||
<body>
|
||||
<div class="container">
|
||||
<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">
|
||||
<input
|
||||
type="text"
|
||||
@@ -106,13 +108,13 @@
|
||||
<div class="upload__column">
|
||||
<h3 class="upload__heading">Recipe data</h3>
|
||||
<label>Title</label>
|
||||
<input value="TEST" required name="title" type="text" />
|
||||
<input value="TEST23" required name="title" type="text" />
|
||||
<label>URL</label>
|
||||
<input value="TEST" required name="sourceUrl" type="text" />
|
||||
<input value="TEST23" required name="sourceUrl" type="text" />
|
||||
<label>Image URL</label>
|
||||
<input value="TEST" required name="image" type="text" />
|
||||
<input value="TEST23" required name="image" type="text" />
|
||||
<label>Publisher</label>
|
||||
<input value="TEST" required name="publisher" type="text" />
|
||||
<input value="TEST23" required name="publisher" type="text" />
|
||||
<label>Prep time</label>
|
||||
<input value="23" required name="cookingTime" type="number" />
|
||||
<label>Servings</label>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const API_URL = 'https://forkify-api.herokuapp.com/api/v2/recipes';
|
||||
export const TIMEOUT_SEC = 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 { MODAL_CLOSE_SEC } from './config.js';
|
||||
import recipeView from './views/recipeView.js';
|
||||
import searchView from './views/searchView.js';
|
||||
import resultsView from './views/resultsView.js';
|
||||
@@ -90,10 +91,35 @@ const controlBookmarks = function () {
|
||||
bookmarksView.render(model.state.bookmarks);
|
||||
};
|
||||
|
||||
const controlAddRecipe = function (newRecipe) {
|
||||
console.log(newRecipe);
|
||||
const controlAddRecipe = async function (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 () {
|
||||
|
||||
+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 {
|
||||
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();
|
||||
|
||||
if (!res.ok) throw new Error(`${data.message} (${res.status})`);
|
||||
|
||||
+56
-18
@@ -1,6 +1,6 @@
|
||||
import { async } from 'regenerator-runtime';
|
||||
import { API_URL, RES_PER_PAGE } from './config';
|
||||
import { getJSON } from './helpers';
|
||||
import { API_URL, RES_PER_PAGE, API_KEY } from './config';
|
||||
import { AJAX } from './helpers';
|
||||
|
||||
export const state = {
|
||||
recipe: {},
|
||||
@@ -13,22 +13,25 @@ export const state = {
|
||||
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) {
|
||||
try {
|
||||
const data = await getJSON(`${API_URL}/${id}`);
|
||||
|
||||
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,
|
||||
};
|
||||
const data = await AJAX(`${API_URL}/${id}?key=${API_KEY}`);
|
||||
state.recipe = createRecipeObject(data);
|
||||
|
||||
if (state.bookmarks.some(bookmark => bookmark.id === id))
|
||||
state.recipe.bookmarked = true;
|
||||
@@ -42,7 +45,7 @@ export const loadSearchResults = async function (query) {
|
||||
try {
|
||||
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 => {
|
||||
return {
|
||||
@@ -50,6 +53,7 @@ export const loadSearchResults = async function (query) {
|
||||
title: rec.title,
|
||||
publisher: rec.publisher,
|
||||
image: rec.image_url,
|
||||
...(rec.key && { key: rec.key }),
|
||||
};
|
||||
});
|
||||
state.search.page = 1;
|
||||
@@ -107,4 +111,38 @@ const init = function () {
|
||||
};
|
||||
|
||||
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._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 {
|
||||
_parentElement = document.querySelector('.upload');
|
||||
_message = 'Recipe was successfully uploaded.';
|
||||
|
||||
_window = document.querySelector('.add-recipe-window');
|
||||
_overlay = document.querySelector('.overlay');
|
||||
|
||||
@@ -15,9 +15,16 @@ class PreviewView extends View {
|
||||
<figure class="preview__fig">
|
||||
<img src="${this._data.image}" alt="${this._data.title}" />
|
||||
</figure>
|
||||
<div class="preview__this._data">
|
||||
<div class="preview__data">
|
||||
<h4 class="preview__title">${this._data.title}</h4>
|
||||
<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>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -76,7 +76,10 @@ class RecipeView extends View {
|
||||
</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>
|
||||
<button class="btn--round btn--bookmark">
|
||||
<svg class="">
|
||||
|
||||
Reference in New Issue
Block a user